saxxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE +22 -0
  6. data/README.md +117 -0
  7. data/Rakefile +12 -0
  8. data/lib/saxxy.rb +2 -0
  9. data/lib/saxxy/activatable.rb +160 -0
  10. data/lib/saxxy/callbacks/libxml.rb +26 -0
  11. data/lib/saxxy/callbacks/nokogiri.rb +30 -0
  12. data/lib/saxxy/callbacks/ox.rb +66 -0
  13. data/lib/saxxy/callbacks/sax.rb +86 -0
  14. data/lib/saxxy/context.rb +88 -0
  15. data/lib/saxxy/context_tree.rb +85 -0
  16. data/lib/saxxy/event.rb +83 -0
  17. data/lib/saxxy/event_registry.rb +122 -0
  18. data/lib/saxxy/node_action.rb +59 -0
  19. data/lib/saxxy/node_rule.rb +90 -0
  20. data/lib/saxxy/parsers/base.rb +28 -0
  21. data/lib/saxxy/parsers/libxml.rb +52 -0
  22. data/lib/saxxy/parsers/nokogiri.rb +28 -0
  23. data/lib/saxxy/parsers/ox.rb +30 -0
  24. data/lib/saxxy/service.rb +47 -0
  25. data/lib/saxxy/utils/agent.rb +66 -0
  26. data/lib/saxxy/utils/callback_array.rb +27 -0
  27. data/lib/saxxy/utils/helpers.rb +13 -0
  28. data/lib/saxxy/version.rb +3 -0
  29. data/saxxy.gemspec +21 -0
  30. data/spec/saxxy/activatable_spec.rb +344 -0
  31. data/spec/saxxy/callbacks/sax_spec.rb +456 -0
  32. data/spec/saxxy/context_spec.rb +51 -0
  33. data/spec/saxxy/context_tree_spec.rb +68 -0
  34. data/spec/saxxy/event_registry_spec.rb +137 -0
  35. data/spec/saxxy/event_spec.rb +49 -0
  36. data/spec/saxxy/node_action_spec.rb +46 -0
  37. data/spec/saxxy/node_rule_spec.rb +99 -0
  38. data/spec/saxxy/parsers/libxml_spec.rb +104 -0
  39. data/spec/saxxy/parsers/nokogiri_spec.rb +200 -0
  40. data/spec/saxxy/parsers/ox_spec.rb +175 -0
  41. data/spec/saxxy/utils/agent_spec.rb +63 -0
  42. data/spec/spec_helper.rb +28 -0
  43. data/spec/support/agent_macros.rb +24 -0
  44. metadata +155 -0
@@ -0,0 +1,3 @@
1
+ module Saxxy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/saxxy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["rubymaniac"]
6
+ gem.description = %q{A Ruby DSL for building SAX parsers.}
7
+ gem.summary = %q{Constructing SAX parsers never been easier.}
8
+ gem.homepage = "https://github.com/rubymaniac/saxxy"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "saxxy"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Saxxy::VERSION
16
+
17
+ gem.add_development_dependency "rake"
18
+ gem.add_development_dependency "rspec", "~> 2.11"
19
+ gem.add_development_dependency "fakeweb"
20
+ gem.add_development_dependency "pry"
21
+ end
@@ -0,0 +1,344 @@
1
+ require 'spec_helper'
2
+ require 'saxxy/node_rule'
3
+ require 'saxxy/activatable'
4
+
5
+
6
+ describe Saxxy::Activatable do
7
+
8
+ let(:klass) do
9
+ Class.new do
10
+ include Saxxy::Activatable
11
+ attr_reader :deactivation_level, :deactivation_callback, :activation_callback, :mode
12
+ end
13
+ end
14
+ let(:subject) { klass.new }
15
+ let(:rule) { Saxxy::NodeRule.new("div", class: /foo$/) }
16
+
17
+
18
+ describe "#included" do
19
+ it "should set an attribute reader for the activation_rule" do
20
+ a_class = Class.new
21
+ a_class.should_receive(:attr_reader).with(:activation_rule)
22
+ Saxxy::Activatable.included(a_class)
23
+ end
24
+ end
25
+
26
+
27
+ describe "#initialize_activatable" do
28
+ it "should set the activation rule" do
29
+ expect { subject.initialize_activatable(rule) }.to change { subject.activation_rule }.from(nil).to(rule)
30
+ end
31
+
32
+ it "should set the deactivation level" do
33
+ expect { subject.initialize_activatable(rule) }.to change {
34
+ subject.deactivation_level
35
+ }.from(nil).to(Saxxy::Activatable::DLEVEL_MIN)
36
+ end
37
+
38
+ it "should set the mode to inactive" do
39
+ expect { subject.initialize_activatable(rule) }.to change { subject.mode }.from(nil).to(:inactive)
40
+ end
41
+ end
42
+
43
+
44
+ describe "#on_deactivation" do
45
+ let(:block) { ->(e) { e } }
46
+
47
+ it "should set the deactivation callback" do
48
+ expect { subject.on_deactivation(&block) }.to change { subject.deactivation_callback }.to(block)
49
+ end
50
+
51
+ it "should return self" do
52
+ subject.on_deactivation(&block).should == subject
53
+ end
54
+ end
55
+
56
+
57
+ describe "#on_activation" do
58
+ let(:block) { ->(e) { e } }
59
+
60
+ it "should set the activation callback" do
61
+ expect { subject.on_activation(&block) }.to change { subject.activation_callback }.to(block)
62
+ end
63
+
64
+ it "should return self" do
65
+ subject.on_activation(&block).should == subject
66
+ end
67
+ end
68
+
69
+
70
+ describe "#can_be_activated_on" do
71
+ context "with activation rule" do
72
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) } }
73
+
74
+ it "should return true if rule matches the node and is closed" do
75
+ subject.stub(closed?: true)
76
+ subject.send(:can_be_activated_on, "div", { class: "baz foo" }).should be_true
77
+ end
78
+
79
+ it "should return false if rule doen't match the node and is closed" do
80
+ subject.stub(closed?: true)
81
+ subject.send(:can_be_activated_on, "div", { class: "baz foo4" }).should be_false
82
+ end
83
+
84
+ it "should return false if rule matches and is not closed" do
85
+ subject.stub(closed?: false)
86
+ subject.send(:can_be_activated_on, "div", { class: "baz foo" }).should be_false
87
+ end
88
+ end
89
+
90
+ context "without activation rule" do
91
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(nil) } }
92
+
93
+ it "should return true on any node if closed" do
94
+ subject.stub(closed?: true)
95
+ subject.send(:can_be_activated_on, "d**", { class: "cREfrt34%^f" }).should be_true
96
+ end
97
+
98
+ it "should return false on any node if not closed" do
99
+ subject.stub(closed?: false)
100
+ subject.send(:can_be_activated_on, "d**", { class: "cREfrt34%^f" }).should be_false
101
+ end
102
+ end
103
+ end
104
+
105
+
106
+ describe "#switch_to" do
107
+ it "should change the mode to the specified argument" do
108
+ expect { subject.send(:switch_to, :active) }.to change { subject.mode }.from(nil).to(:active)
109
+ end
110
+ end
111
+
112
+
113
+ describe "#is" do
114
+ it "should return true if is in the same mode as the argument" do
115
+ subject.send(:switch_to, :active)
116
+ subject.send(:is, :active).should be_true
117
+ end
118
+
119
+ it "should return false if is not in the same mode as the argument" do
120
+ subject.send(:switch_to, :inactive)
121
+ subject.send(:is, :active).should be_false
122
+ end
123
+ end
124
+
125
+
126
+ describe "#activate_on" do
127
+ context "inactive mode" do
128
+ let(:callback) { ->(e) { e } }
129
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) }.on_activation(&callback) }
130
+
131
+ it "should not run the callback if rule does not match" do
132
+ args1 = ["div", { class: "foo4" }]
133
+ args2 = ["span", { class: "foo" }]
134
+ subject.activation_rule.matches(*args1).should be_false
135
+ subject.activation_rule.matches(*args2).should be_false
136
+ callback.should_not_receive(:call)
137
+ subject.activate_on(*args1)
138
+ subject.activate_on(*args2)
139
+ end
140
+
141
+ it "should run the callback if it has rule and matches the node" do
142
+ args = ["div", { class: "foo" }]
143
+ subject.activation_rule.matches(*args).should be_true
144
+ callback.should_receive(:call)
145
+ subject.activate_on(*args)
146
+ end
147
+
148
+ it "should not increment the level if it cannot be activated" do
149
+ expect { subject.activate_on("div", class: "foo4") }.to_not change { subject.deactivation_level }
150
+ expect { subject.activate_on("span", class: "foo") }.to_not change { subject.deactivation_level }
151
+ end
152
+
153
+ it "should increment the level if it can be activated" do
154
+ expect { subject.activate_on("div", class: "foo") }.to change { subject.deactivation_level }.by(1)
155
+ end
156
+
157
+ it "should change the mode if it can be activated" do
158
+ expect { subject.activate_on("div", class: "foo") }.to change {
159
+ subject.mode
160
+ }.from(:inactive).to(:active)
161
+ end
162
+ end
163
+
164
+ context "active mode" do
165
+ let(:subject) do
166
+ klass.new.tap do |i|
167
+ i.initialize_activatable(rule)
168
+ i.activate_on("div", class: "foo")
169
+ end
170
+ end
171
+
172
+ before { subject.send(:is, :active).should be_true }
173
+
174
+ it "should not run the callback if it can be activated" do
175
+ subject.on_activation(&->(){})
176
+ subject.activation_callback.should_not_receive(:call)
177
+ subject.activate_on("div", class: "foo")
178
+ end
179
+
180
+ it "should not increment the level if rule doesn't match element name" do
181
+ expect { subject.activate_on("div3", class: "foo") }.to_not change { subject.deactivation_level }
182
+ end
183
+
184
+ it "should increment the level if rule matches element name" do
185
+ expect { subject.activate_on("div", class: "foo43") }.to change { subject.deactivation_level }.by(1)
186
+ end
187
+
188
+ it "should not change the mode if rule matches element name" do
189
+ expect { subject.activate_on("div", class: "foo") }.to_not change { subject.mode }
190
+ end
191
+ end
192
+ end
193
+
194
+
195
+ describe "#deactivate_on" do
196
+ let(:callback) { ->(e) { e } }
197
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) }.on_deactivation(&callback) }
198
+
199
+ context "inactive mode" do
200
+ it "should not call decrement_level or deactivate! if rule does not match element name" do
201
+ callback.should_not_receive(:call)
202
+ subject.should_not_receive(:decrement_level)
203
+ subject.should_not_receive(:deactivate!)
204
+ subject.deactivate_on("divs")
205
+ end
206
+
207
+ it "should not call decrement_level or deactivate! if rule matches element name" do
208
+ callback.should_not_receive(:call)
209
+ subject.should_not_receive(:decrement_level)
210
+ subject.should_not_receive(:deactivate!)
211
+ subject.deactivate_on("div")
212
+ end
213
+
214
+ it "should not change the mode when is deactivated in a matching element name" do
215
+ expect { subject.deactivate_on("div") }.to_not change { subject.mode }
216
+ end
217
+
218
+ it "should not change the mode when is deactivated in non matching element name" do
219
+ expect { subject.deactivate_on("divd") }.to_not change { subject.mode }
220
+ end
221
+ end
222
+
223
+ context "active mode" do
224
+ before do
225
+ subject.activate_on("div", class: "foo")
226
+ subject.send(:is, :active).should be_true
227
+ end
228
+
229
+ it "should not run the callback if it isnt deactivated as many times as gets activated i.e. is not closed" do
230
+ callback.should_not_receive(:call).with(subject)
231
+ 4.times { |i| subject.activate_on("div", class: "foo#{i}") }
232
+ 4.times { subject.deactivate_on("div") }
233
+ end
234
+
235
+ it "should run the deactivation callback if it is deactivated as many times as gets activated i.e. is closed" do
236
+ callback.should_receive(:call).with(subject)
237
+ 3.times { |i| subject.activate_on("div", class: "foo#{i}") }
238
+ 4.times { subject.deactivate_on("div") }
239
+ end
240
+
241
+ it "should not change mode to inactive if it isnt deactivated as many times as gets activated i.e. is not closed" do
242
+ expect do
243
+ 4.times { |i| subject.activate_on("div", class: "foo#{i}") }
244
+ 4.times { subject.deactivate_on("div") }
245
+ end.to_not change { subject.mode }
246
+ end
247
+
248
+ it "should change mode to inactive if it is deactivated as many times as gets activated i.e. is closed" do
249
+ expect do
250
+ 3.times { |i| subject.activate_on("div", class: "foo#{i}") }
251
+ 4.times { subject.deactivate_on("div") }
252
+ end.to change { subject.mode }.from(:active).to(:inactive)
253
+ end
254
+
255
+ it "should run the deactivation callback if rule matches element name" do
256
+ callback.should_receive(:call).with(subject)
257
+ subject.deactivate_on("div")
258
+ end
259
+
260
+ it "should not run the deactivation callback if rule doesn't match element name" do
261
+ callback.should_not_receive(:call).with(subject)
262
+ subject.deactivate_on("divd")
263
+ end
264
+
265
+ it "should decrement the level when deactivated on a matching element name" do
266
+ expect { subject.deactivate_on("div") }.to change { subject.deactivation_level }.by(-1)
267
+ end
268
+
269
+ it "should not decrement the level when deactivated on a non matching element name" do
270
+ expect { subject.deactivate_on("divd") }.to_not change { subject.deactivation_level }
271
+ end
272
+ end
273
+ end
274
+
275
+
276
+ describe "#run_activation_callback" do
277
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) } }
278
+
279
+ it "should call the activation_callback if it is present" do
280
+ subject.on_activation(&->(){})
281
+ subject.instance_variable_get("@activation_callback").should_receive(:call).with(subject)
282
+ subject.send(:run_activation_callback)
283
+ end
284
+
285
+ it "should not call the activation_callback if it is not present" do
286
+ allow_message_expectations_on_nil
287
+ subject.instance_variable_get("@activation_callback").should_not_receive(:call).with(subject)
288
+ subject.send(:run_activation_callback)
289
+ end
290
+ end
291
+
292
+
293
+ describe "#run_deactivation_callback" do
294
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) } }
295
+
296
+ it "should call the deactivation_callback if it is present" do
297
+ subject.on_deactivation(&->(){})
298
+ subject.instance_variable_get("@deactivation_callback").should_receive(:call).with(subject)
299
+ subject.send(:run_deactivation_callback)
300
+ end
301
+
302
+ it "should not call the deactivation callback if it is not present" do
303
+ allow_message_expectations_on_nil
304
+ subject.instance_variable_get("@deactivation_callback").should_not_receive(:call).with(subject)
305
+ subject.send(:run_deactivation_callback)
306
+ end
307
+ end
308
+
309
+
310
+ describe "#increment_level" do
311
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) } }
312
+
313
+ it "should increment the deactivation_level" do
314
+ expect { subject.send(:increment_level) }.to change { subject.deactivation_level }.by(1)
315
+ end
316
+ end
317
+
318
+
319
+ describe "#decrement_level" do
320
+ let(:subject) { klass.new.tap { |i| i.initialize_activatable(rule) } }
321
+
322
+ it "should decrement the deactivation_level" do
323
+ expect { subject.send(:decrement_level) }.to change { subject.deactivation_level }.by(-1)
324
+ end
325
+ end
326
+
327
+
328
+ describe "#rule_matches_element_name" do
329
+ it "should return true if no activation rule" do
330
+ subject.send(:rule_matches_element_name, "foo").should be_true
331
+ end
332
+
333
+ it "should return false if it has activation_rule and does not match the element name" do
334
+ subject.initialize_activatable(rule)
335
+ subject.send(:rule_matches_element_name, "foo").should be_false
336
+ end
337
+
338
+ it "should return true if it has activation_rule and does match the element name" do
339
+ subject.initialize_activatable(rule)
340
+ subject.send(:rule_matches_element_name, "div").should be_true
341
+ end
342
+ end
343
+
344
+ end
@@ -0,0 +1,456 @@
1
+ require "saxxy/utils/callback_array"
2
+ require "saxxy/context"
3
+ require "saxxy/node_rule"
4
+ require "saxxy/event_registry"
5
+ require "saxxy/context_tree"
6
+ require "saxxy/callbacks/sax"
7
+
8
+
9
+ describe Saxxy::Callbacks::SAX do
10
+
11
+ # A class that includes the SAXCallbacks functionality
12
+ let(:klass) do
13
+ Class.new do
14
+ include Saxxy::Callbacks::SAX
15
+ attr_reader :active_pool, :inactive_pool, :event_registry
16
+ end
17
+ end
18
+
19
+ # Helper methods
20
+ def get_level(obj = subject)
21
+ obj.instance_variable_get("@deactivation_level")
22
+ end
23
+
24
+ def pool(type = :active, obj = subject)
25
+ obj.instance_variable_get("@#{type}_pool")
26
+ end
27
+
28
+ def registry
29
+ subject.event_registry.instance_variable_get("@actions")
30
+ end
31
+
32
+ def events
33
+ registry.values
34
+ end
35
+
36
+ def actions
37
+ registry.keys
38
+ end
39
+
40
+
41
+ describe "#initialize" do
42
+ let(:context) do
43
+ Saxxy::ContextTree.new do
44
+ under("div", class: "foo") do
45
+ under("span", title: /bar/)
46
+ end
47
+
48
+ under("table")
49
+ end.root
50
+ end
51
+ let(:subject) { klass.new(context) }
52
+
53
+ it "should set the active pool to a callback array" do
54
+ subject.active_pool.should be_a(Saxxy::CallbackArray)
55
+ subject.active_pool.instance_variable_get("@add_callback").should_not be_nil
56
+ end
57
+
58
+ it "should set the inactive pool to a callback array" do
59
+ subject.inactive_pool.should be_a(Saxxy::CallbackArray)
60
+ subject.inactive_pool.instance_variable_get("@add_callback").should_not be_nil
61
+ end
62
+
63
+ it "should set the event registry" do
64
+ subject.instance_variable_get("@event_registry").should be_a(Saxxy::EventRegistry)
65
+ end
66
+
67
+ it "should have the context in the active_pool" do
68
+ subject.active_pool.to_a.should == [context]
69
+ end
70
+
71
+ it "should have the children of the context in the inactive_pool" do
72
+ subject.inactive_pool.to_a.should have(2).contexts
73
+ subject.inactive_pool.to_a.should == context.child_contexts
74
+ end
75
+ end
76
+
77
+
78
+ describe "#on_start_element" do
79
+ let(:context) do
80
+ Saxxy::ContextTree.new do
81
+ under("div", class: "foo") do
82
+ on("ul") {}
83
+ under("span", title: /bar/) {}
84
+ end
85
+
86
+ under("table") do
87
+ on("tr") {}
88
+ end
89
+ end.root
90
+ end
91
+ let(:subject) { klass.new(context) }
92
+
93
+ it "should not add any contexts to active pool if there is no context matching on the level" do
94
+ expect { subject.on_start_element("div4", class: "foo") }.to_not change { subject.active_pool.to_a }
95
+ end
96
+
97
+ it "should add the matching contexts to the active pool" do
98
+ subject.active_pool.to_a.should have(1).context
99
+ subject.on_start_element("div", class: "foo")
100
+ subject.active_pool.to_a.should have(2).contexts
101
+ subject.active_pool.to_a.last.activation_rule.element.should == "div"
102
+ end
103
+
104
+ it "should remove the matching contexts from tha inactive pool and add them to the active" do
105
+ matching = subject.inactive_pool.to_a.first
106
+ subject.on_start_element("div", class: "foo")
107
+ subject.inactive_pool.to_a.should_not include(matching)
108
+ subject.active_pool.to_a.should include(matching)
109
+ end
110
+
111
+ it "should add the child contexts of the matching ones to the inactive pool" do
112
+ expect { subject.on_start_element("div", class: "foo") }.to change {
113
+ subject.inactive_pool.to_a.map { |c| c.activation_rule.element }
114
+ }.from(["div", "table"]).to(["table", "span"])
115
+ end
116
+
117
+ it "should add the actions of the active contexts to the event registry" do
118
+ expect do
119
+ subject.on_start_element("div", class: "foo")
120
+ subject.on_start_element("ul", class: "whateva")
121
+ end.to change { actions.map { |a| a.activation_rule.element } }.from([]).to(["ul"])
122
+ end
123
+
124
+ it "should not add the actions of the inactive contexts to the registry" do
125
+ expect do
126
+ subject.on_start_element("table", class: "foo")
127
+ subject.on_start_element("tr", class: "whateva")
128
+ end.to_not change { actions.map { |a| a.activation_rule.element } }.from([]).to(["ul"])
129
+ end
130
+
131
+ it "should change the level on the activated contexts" do
132
+ matching = subject.inactive_pool.to_a.first
133
+ expect { subject.on_start_element("div", class: "foo") }.to change { get_level(matching) }.by(1)
134
+ end
135
+
136
+ it "should open the activated contexts" do
137
+ matching = subject.inactive_pool.to_a.first
138
+ expect do
139
+ subject.on_start_element("div", class: "foo")
140
+ end.to change { matching.send(:closed?) }.from(true).to(false)
141
+ end
142
+
143
+ it "should add an event if an action mathces the node" do
144
+ expect do
145
+ subject.on_start_element("div", class: "foo")
146
+ 3.times { subject.on_start_element("ul", class: "whateva") }
147
+ end.to change { registry.values.flatten.size }.by(3)
148
+ end
149
+
150
+ it "should change the level on the activated events if matches node" do
151
+ expect do
152
+ subject.on_start_element("div", class: "foo")
153
+ subject.on_start_element("ul", class: "whateva")
154
+ end.to change {
155
+ registry.values.first ? get_level(registry.values.first.last) : Saxxy::Activatable::DLEVEL_MIN
156
+ }.by(1)
157
+ end
158
+
159
+ it "should not change the level on previously activated events that match the node" do
160
+ subject.on_start_element("div", class: "foo")
161
+ subject.on_start_element("ul", class: "whateva")
162
+ previous = registry.values.first.last
163
+ expect { subject.on_start_element("ul", class: "whateva") }.to_not change { get_level(previous) }
164
+ end
165
+
166
+ it "should not change the level on previously activated events that do not match the node" do
167
+ subject.on_start_element("div", class: "foo")
168
+ subject.on_start_element("ul", class: "whateva")
169
+ previous = registry.values.first.last
170
+ expect { subject.on_start_element("ul2", class: "whateva") }.to_not change { get_level(previous) }
171
+ end
172
+
173
+ it "should call register_and_activate_events_on" do
174
+ subject.should_receive(:register_and_activate_events_on).with("ul", class: "foo")
175
+ subject.on_start_element("ul", class: "foo")
176
+ end
177
+
178
+ it "should call register_event_from_action on any matching action" do
179
+ subject.on_start_element("div", class: "foo")
180
+ subject.on_start_element("table", class: "foo")
181
+ subject.should have(2).actions
182
+ matching = subject.send(:actions).select { |a| a.matches("ul", class: "whateva") }.first
183
+ subject.should_receive(:register_event_from_action).with(matching, "ul", class: "whateva")
184
+ subject.on_start_element("ul", class: "whateva")
185
+ end
186
+
187
+ it "should call activate_events_on" do
188
+ subject.should_receive(:activate_events_on).with("ul", class: "foo")
189
+ subject.on_start_element("ul", class: "foo")
190
+ end
191
+
192
+ it "should call register_and_activate_events_on" do
193
+ subject.should_receive(:activate_contexts_on).with("ul", class: "foo")
194
+ subject.on_start_element("ul", class: "foo")
195
+ end
196
+ end
197
+
198
+
199
+ describe "#on_characters" do
200
+ let(:context) do
201
+ Saxxy::ContextTree.new do
202
+ under("div", class: "foo") do
203
+ on("ul") {}
204
+ on("span") {}
205
+ under("span", title: /bar/) {}
206
+ end
207
+
208
+ under("table") do
209
+ on("tr") {}
210
+ end
211
+ end.root
212
+ end
213
+ let(:subject) { klass.new(context) }
214
+
215
+ # Activate the first context and the first action
216
+ before do
217
+ subject.on_start_element("div", class: "foo")
218
+ subject.on_start_element("ul", {})
219
+ end
220
+
221
+ it "should add the text on the last activated event for the matching actions" do
222
+ expect do
223
+ subject.on_start_element("ul", {})
224
+ subject.on_characters("some chars")
225
+ end.to change { events.flatten.map(&:text) }.from([""]).to(["", "some chars"])
226
+ end
227
+
228
+ it "should append the text for as many times it is called" do
229
+ expect do
230
+ subject.on_characters("count")
231
+ 5.times { |i| subject.on_characters(" #{i+1}") }
232
+ end.to change { events.flatten.map(&:text) }.from([""]).to(["count 1 2 3 4 5"])
233
+ end
234
+
235
+ it "should append nothing if called with a nil argument" do
236
+ expect do
237
+ subject.on_characters(nil)
238
+ end.to_not change { events.flatten.map(&:text) }
239
+ end
240
+ end
241
+
242
+
243
+ describe "#on_end_element" do
244
+ let(:context) do
245
+ Saxxy::ContextTree.new do
246
+ under("div", class: "foo") do
247
+ on("ul") {}
248
+ on("span") {}
249
+ under("span", title: /bar/) {}
250
+ end
251
+
252
+ under("table") do
253
+ on("tr") {}
254
+ on("td") {}
255
+ end
256
+ end.root
257
+ end
258
+ let(:subject) { klass.new(context) }
259
+
260
+ context "events" do
261
+ # Activate an event
262
+ before do
263
+ subject.on_start_element("div", class: "foo")
264
+ subject.on_start_element("ul", {})
265
+ end
266
+
267
+ it "should remove the events that are closed from the registry" do
268
+ # Activate another event in order to have 2 events for that action
269
+ subject.on_start_element("ul", {})
270
+ events.flatten.should have(2).events
271
+ removed_event = events.flatten.last
272
+ removed_event.should_not be_closed
273
+ subject.on_end_element("ul")
274
+ removed_event.should be_closed
275
+ events.flatten.should_not include(removed_event)
276
+ end
277
+
278
+ it "should call the action of the closed events with the appended text" do
279
+ removed_event = events.flatten.last
280
+ removed_event.action.should_receive(:call).with("foo", "ul", {})
281
+ subject.on_characters("foo")
282
+ subject.on_end_element("ul")
283
+ removed_event.should be_closed
284
+ end
285
+
286
+ it "should not remove the still opened events" do
287
+ subject.on_start_element("ul", {})
288
+ events.flatten.should have(2).events
289
+ not_removed = events.flatten.first
290
+ subject.on_end_element("ul")
291
+ events.flatten.should include(not_removed)
292
+ end
293
+
294
+ # Note: If we assert that the action of the not removed event should not
295
+ # receive :call it will be a false assertion due to the fact that
296
+ # the events are pointing to the same action object.
297
+ it "should not call the action of the still opened events" do
298
+ subject.on_start_element("ul", {})
299
+ not_removed = events.flatten.first
300
+ not_removed.should_not_receive(:fire)
301
+ subject.on_end_element("ul")
302
+ not_removed.should_not be_closed
303
+ end
304
+ end
305
+
306
+ context "contexts" do
307
+ before { subject.on_start_element("table", class: "foo") }
308
+
309
+ it "should remove a closed context from the active pool" do
310
+ removed = pool.to_a.last
311
+ pool.to_a.should include(removed)
312
+ subject.on_end_element("table")
313
+ pool.to_a.should_not include(removed)
314
+ end
315
+
316
+ it "should not remove an open context from the active pool" do
317
+ not_removed = pool.last
318
+ pool.should include(not_removed)
319
+ subject.on_end_element("foo")
320
+ pool.should include(not_removed)
321
+ end
322
+
323
+ it "should not empty the active pool if there is other contexts in" do
324
+ subject.on_start_element("div", class: "foo")
325
+ subject.on_start_element("span", title: "foobar")
326
+ expect do
327
+ subject.on_end_element("span")
328
+ subject.on_end_element("div")
329
+ end.to change { pool.length }.from(4).to(2)
330
+ end
331
+ end
332
+
333
+
334
+ context "private methods" do
335
+ let(:context) do
336
+ Saxxy::ContextTree.new do
337
+ under("div", class: "foo") do
338
+ on("ul") {}
339
+ on("span") {}
340
+ under("span", title: /bar/) {}
341
+ end
342
+
343
+ under("table") do
344
+ on("tr") {}
345
+ on("td") {}
346
+ end
347
+ end.root
348
+ end
349
+ let(:subject) { klass.new(context) }
350
+
351
+
352
+ describe "#actions" do
353
+ before { subject.on_start_element("table", class: "foo") }
354
+
355
+ it "should contain the actions of the active contexts" do
356
+ subject.send(:actions).should == pool.last.actions
357
+ end
358
+
359
+ it "should not contain the actions of the inactive contexts" do
360
+ actions = subject.send(:actions)
361
+ actions.length.should_not be_zero
362
+ pool(:inactive).flat_map(&:actions).each do |action|
363
+ actions.should_not include(action)
364
+ end
365
+ end
366
+ end
367
+
368
+
369
+ describe "#on_remove_from_active_pool" do
370
+ # This will happen only if the html is malformed
371
+ it "should remove actions that are still in the registry" do
372
+ subject.on_start_element("table", class: "foo")
373
+ subject.on_start_element("tr", class: "foo")
374
+ expect do
375
+ subject.send(:on_remove_from_active_pool, pool.last)
376
+ end.to change { registry.values.empty? }.from(false).to(true)
377
+ end
378
+
379
+ it "should push the context back to the inactive pool if it's parent is in the active pool"
380
+ end
381
+
382
+
383
+ describe "#on_add_to_active_pool" do
384
+ it "will transfer it's child contexts into the inactive pool" do
385
+ div_context = context.child_contexts.first
386
+ div_context.child_contexts.should_not be_empty
387
+ div_context.child_contexts.each { |c| pool(:inactive).should_not include(c) }
388
+ subject.send(:on_add_to_active_pool, div_context)
389
+ div_context.child_contexts.each { |c| pool(:inactive).should include(c) }
390
+ end
391
+
392
+ it "should set the on_deactivation callback on the context" do
393
+ ctx = Saxxy::Context.new
394
+ ctx.should_receive(:on_deactivation)
395
+ subject.send(:on_add_to_active_pool, ctx)
396
+ end
397
+ end
398
+
399
+
400
+ describe "#on_add_to_inactive_pool" do
401
+ it "should set the on_activation callback on the context" do
402
+ ctx = Saxxy::Context.new
403
+ ctx.should_receive(:on_activation)
404
+ subject.send(:on_add_to_inactive_pool, ctx)
405
+ end
406
+ end
407
+
408
+
409
+ describe "#deactivate_contexts_on" do
410
+ it "should call deactivate_on on every context in active pool" do
411
+ subject.on_start_element("table")
412
+ pool.each { |c| c.should_receive(:deactivate_on).with("span") }
413
+ subject.send(:deactivate_contexts_on, "span")
414
+ end
415
+
416
+ it "should not call deactivate_on on contexts in inactive pool" do
417
+ subject.on_start_element("table")
418
+ pool(:inactive).each { |c| c.should_not_receive(:deactivate_on) }
419
+ subject.send(:deactivate_contexts_on, "span")
420
+ end
421
+ end
422
+
423
+
424
+ describe "#activate_contexts_on" do
425
+ it "should call deactivate_on on every context in active pool" do
426
+ subject.on_start_element("table")
427
+ pool(:inactive).each { |c| c.should_receive(:activate_on).with("span", class: "foo") }
428
+ subject.send(:activate_contexts_on, "span", class: "foo")
429
+ end
430
+
431
+ it "should not call deactivate_on on contexts in inactive pool" do
432
+ subject.on_start_element("table")
433
+ pool.each { |c| c.should_not_receive(:activate_on) }
434
+ subject.send(:activate_contexts_on, "span", class: "foo")
435
+ end
436
+ end
437
+
438
+
439
+ describe "#register_and_activate_events_on" do
440
+ it "should call register_event_from_action with any matching action as argument" do
441
+ a1, a2 = Object.new, Object.new
442
+ a1.should_receive(:matches).and_return(true)
443
+ a2.should_receive(:matches).and_return(false)
444
+ subject.stub(actions: [a1, a2])
445
+ subject.should_receive(:register_event_from_action).with(a1, "div", class: "foo")
446
+ subject.send(:register_and_activate_events_on, "div", class: "foo")
447
+ end
448
+
449
+ it "should call the activate_events_on with same arguments" do
450
+ subject.should_receive(:activate_events_on).with("div", class: "foo")
451
+ subject.send(:register_and_activate_events_on, "div", class: "foo")
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end