saxxy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|