saxxy 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +5 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +117 -0
- data/Rakefile +12 -0
- data/lib/saxxy.rb +2 -0
- data/lib/saxxy/activatable.rb +160 -0
- data/lib/saxxy/callbacks/libxml.rb +26 -0
- data/lib/saxxy/callbacks/nokogiri.rb +30 -0
- data/lib/saxxy/callbacks/ox.rb +66 -0
- data/lib/saxxy/callbacks/sax.rb +86 -0
- data/lib/saxxy/context.rb +88 -0
- data/lib/saxxy/context_tree.rb +85 -0
- data/lib/saxxy/event.rb +83 -0
- data/lib/saxxy/event_registry.rb +122 -0
- data/lib/saxxy/node_action.rb +59 -0
- data/lib/saxxy/node_rule.rb +90 -0
- data/lib/saxxy/parsers/base.rb +28 -0
- data/lib/saxxy/parsers/libxml.rb +52 -0
- data/lib/saxxy/parsers/nokogiri.rb +28 -0
- data/lib/saxxy/parsers/ox.rb +30 -0
- data/lib/saxxy/service.rb +47 -0
- data/lib/saxxy/utils/agent.rb +66 -0
- data/lib/saxxy/utils/callback_array.rb +27 -0
- data/lib/saxxy/utils/helpers.rb +13 -0
- data/lib/saxxy/version.rb +3 -0
- data/saxxy.gemspec +21 -0
- data/spec/saxxy/activatable_spec.rb +344 -0
- data/spec/saxxy/callbacks/sax_spec.rb +456 -0
- data/spec/saxxy/context_spec.rb +51 -0
- data/spec/saxxy/context_tree_spec.rb +68 -0
- data/spec/saxxy/event_registry_spec.rb +137 -0
- data/spec/saxxy/event_spec.rb +49 -0
- data/spec/saxxy/node_action_spec.rb +46 -0
- data/spec/saxxy/node_rule_spec.rb +99 -0
- data/spec/saxxy/parsers/libxml_spec.rb +104 -0
- data/spec/saxxy/parsers/nokogiri_spec.rb +200 -0
- data/spec/saxxy/parsers/ox_spec.rb +175 -0
- data/spec/saxxy/utils/agent_spec.rb +63 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/agent_macros.rb +24 -0
- metadata +155 -0
data/saxxy.gemspec
ADDED
@@ -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
|