blather 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1 -0
- data/LICENSE +20 -0
- data/Manifest +43 -0
- data/README.rdoc +78 -0
- data/Rakefile +16 -0
- data/blather.gemspec +41 -0
- data/examples/echo.rb +22 -0
- data/examples/shell_client.rb +28 -0
- data/lib/autotest/discover.rb +1 -0
- data/lib/autotest/spec.rb +60 -0
- data/lib/blather.rb +46 -0
- data/lib/blather/callback.rb +24 -0
- data/lib/blather/client.rb +81 -0
- data/lib/blather/core/errors.rb +24 -0
- data/lib/blather/core/jid.rb +101 -0
- data/lib/blather/core/roster.rb +84 -0
- data/lib/blather/core/roster_item.rb +92 -0
- data/lib/blather/core/stanza.rb +116 -0
- data/lib/blather/core/stanza/iq.rb +27 -0
- data/lib/blather/core/stanza/iq/query.rb +42 -0
- data/lib/blather/core/stanza/iq/roster.rb +96 -0
- data/lib/blather/core/stanza/message.rb +55 -0
- data/lib/blather/core/stanza/presence.rb +35 -0
- data/lib/blather/core/stanza/presence/status.rb +77 -0
- data/lib/blather/core/stanza/presence/subscription.rb +73 -0
- data/lib/blather/core/stream.rb +181 -0
- data/lib/blather/core/stream/parser.rb +74 -0
- data/lib/blather/core/stream/resource.rb +51 -0
- data/lib/blather/core/stream/sasl.rb +135 -0
- data/lib/blather/core/stream/session.rb +43 -0
- data/lib/blather/core/stream/tls.rb +29 -0
- data/lib/blather/core/sugar.rb +150 -0
- data/lib/blather/core/xmpp_node.rb +132 -0
- data/lib/blather/extensions.rb +4 -0
- data/lib/blather/extensions/last_activity.rb +55 -0
- data/lib/blather/extensions/version.rb +85 -0
- data/spec/blather/core/jid_spec.rb +78 -0
- data/spec/blather/core/roster_item_spec.rb +80 -0
- data/spec/blather/core/roster_spec.rb +79 -0
- data/spec/blather/core/stanza_spec.rb +95 -0
- data/spec/blather/core/stream_spec.rb +263 -0
- data/spec/blather/core/xmpp_node_spec.rb +130 -0
- data/spec/build_safe.rb +20 -0
- data/spec/spec_helper.rb +49 -0
- metadata +172 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::Stanza' do
|
4
|
+
it 'provides .next_id helper for generating new IDs' do
|
5
|
+
proc { Stanza.next_id }.must_change 'Stanza', :next_id
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can import a node' do
|
9
|
+
s = Stanza.import XMPPNode.new('foo')
|
10
|
+
s.element_name.must_equal 'foo'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'sets the ID when created' do
|
14
|
+
Stanza.new('message').id.wont_be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'sets the document when created' do
|
18
|
+
Stanza.new('message').doc.wont_be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'provides an #error? helper' do
|
22
|
+
s = Stanza.new('message')
|
23
|
+
s.error?.must_equal false
|
24
|
+
s.type = :error
|
25
|
+
s.error?.must_equal true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'will generate a reply' do
|
29
|
+
s = Stanza.new('message')
|
30
|
+
s.from = f = JID.new('n@d/r')
|
31
|
+
s.to = t = JID.new('d@n/r')
|
32
|
+
|
33
|
+
r = s.reply
|
34
|
+
r.object_id.wont_equal s.object_id
|
35
|
+
r.from.must_equal t
|
36
|
+
r.to.must_equal f
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'convert to a reply' do
|
40
|
+
s = Stanza.new('message')
|
41
|
+
s.from = f = JID.new('n@d/r')
|
42
|
+
s.to = t = JID.new('d@n/r')
|
43
|
+
|
44
|
+
r = s.reply!
|
45
|
+
r.object_id.must_equal s.object_id
|
46
|
+
r.from.must_equal t
|
47
|
+
r.to.must_equal f
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'provides "attr_accessor" for id' do
|
51
|
+
s = Stanza.new('message')
|
52
|
+
s.id.wont_be_nil
|
53
|
+
s['id'].wont_be_nil
|
54
|
+
|
55
|
+
s.id = nil
|
56
|
+
s.id.must_be_nil
|
57
|
+
s['id'].must_be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'provides "attr_accessor" for to' do
|
61
|
+
s = Stanza.new('message')
|
62
|
+
s.to.must_be_nil
|
63
|
+
s['to'].must_be_nil
|
64
|
+
|
65
|
+
s.to = JID.new('n@d/r')
|
66
|
+
s.to.wont_be_nil
|
67
|
+
s.to.must_be_kind_of JID
|
68
|
+
|
69
|
+
s['to'].wont_be_nil
|
70
|
+
s['to'].must_equal 'n@d/r'
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'provides "attr_accessor" for from' do
|
74
|
+
s = Stanza.new('message')
|
75
|
+
s.from.must_be_nil
|
76
|
+
s['from'].must_be_nil
|
77
|
+
|
78
|
+
s.from = JID.new('n@d/r')
|
79
|
+
s.from.wont_be_nil
|
80
|
+
s.from.must_be_kind_of JID
|
81
|
+
|
82
|
+
s['from'].wont_be_nil
|
83
|
+
s['from'].must_equal 'n@d/r'
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'provides "attr_accessor" for type' do
|
87
|
+
s = Stanza.new('message')
|
88
|
+
s.type.must_be_nil
|
89
|
+
s['type'].must_be_nil
|
90
|
+
|
91
|
+
s.type = 'testing'
|
92
|
+
s.type.wont_be_nil
|
93
|
+
s['type'].wont_be_nil
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::Stream' do
|
4
|
+
class MockStream; include Stream; end
|
5
|
+
def mock_stream(&block)
|
6
|
+
@client = mock()
|
7
|
+
@client.stubs(:jid=)
|
8
|
+
stream = MockStream.new @client, JID.new('n@d/r'), 'pass'
|
9
|
+
|
10
|
+
stream.expects(:send_data).at_least(1).with &block
|
11
|
+
stream
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be started' do
|
15
|
+
client = mock()
|
16
|
+
params = [client, 'n@d/r', 'pass', 'host', 1234]
|
17
|
+
EM.expects(:connect).with do |*parms|
|
18
|
+
parms[0] == 'host' &&
|
19
|
+
parms[1] == 1234 &&
|
20
|
+
parms[3] == client &&
|
21
|
+
parms[5] == 'pass' &&
|
22
|
+
parms[4] == JID.new('n@d/r')
|
23
|
+
end
|
24
|
+
|
25
|
+
Stream.start *(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can figure out the host to use based on the jid' do
|
29
|
+
client = mock()
|
30
|
+
params = [client, 'n@d/r', 'pass', 'd', 5222]
|
31
|
+
EM.expects(:connect).with do |*parms|
|
32
|
+
parms[0] == 'd' &&
|
33
|
+
parms[1] == 5222 &&
|
34
|
+
parms[3] == client &&
|
35
|
+
parms[5] == 'pass' &&
|
36
|
+
parms[4] == JID.new('n@d/r')
|
37
|
+
end
|
38
|
+
|
39
|
+
Stream.start client, 'n@d/r', 'pass'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'starts the stream once the connection is complete' do
|
43
|
+
s = mock_stream { |d| d =~ /stream:stream/ }
|
44
|
+
s.connection_completed
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'starts TLS when asked' do
|
48
|
+
state = nil
|
49
|
+
@stream = mock_stream do |val|
|
50
|
+
case
|
51
|
+
when state.nil? && val =~ /stream:stream/ then state = :started
|
52
|
+
when state == :started && val =~ /starttls/ then true
|
53
|
+
else false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@stream.connection_completed
|
57
|
+
@stream.receive_data "<stream:stream><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'connects via SASL MD5 when asked' do
|
61
|
+
Time.any_instance.stubs(:to_f).returns(1.1)
|
62
|
+
|
63
|
+
state = nil
|
64
|
+
client = mock()
|
65
|
+
client.stubs(:jid=)
|
66
|
+
stream = MockStream.new client, JID.new('n@d/r'), 'pass'
|
67
|
+
|
68
|
+
stream.expects(:send_data).times(5).with do |val|
|
69
|
+
case state
|
70
|
+
when nil
|
71
|
+
val.must_match(/stream:stream/)
|
72
|
+
state = :started
|
73
|
+
stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features>"
|
74
|
+
true
|
75
|
+
|
76
|
+
when :started
|
77
|
+
val.must_match(/auth.*DIGEST\-MD5/)
|
78
|
+
state = :auth_sent
|
79
|
+
stream.receive_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>"
|
80
|
+
true
|
81
|
+
|
82
|
+
when :auth_sent
|
83
|
+
val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2Qi</response>')
|
84
|
+
state = :response1_sent
|
85
|
+
stream.receive_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
|
86
|
+
true
|
87
|
+
|
88
|
+
when :response1_sent
|
89
|
+
val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>')
|
90
|
+
state = :response2_sent
|
91
|
+
stream.receive_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"
|
92
|
+
true
|
93
|
+
|
94
|
+
when :response2_sent
|
95
|
+
val.must_match(/stream:stream/)
|
96
|
+
state = :complete
|
97
|
+
true
|
98
|
+
|
99
|
+
else
|
100
|
+
false
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
stream.connection_completed
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'will connect via SSL PLAIN when asked' do
|
108
|
+
state = nil
|
109
|
+
client = mock()
|
110
|
+
client.stubs(:jid=)
|
111
|
+
stream = MockStream.new client, JID.new('n@d/r'), 'pass'
|
112
|
+
|
113
|
+
stream.expects(:send_data).times(3).with do |val|
|
114
|
+
case state
|
115
|
+
when nil
|
116
|
+
val.must_match(/stream:stream/)
|
117
|
+
state = :started
|
118
|
+
stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
|
119
|
+
true
|
120
|
+
|
121
|
+
when :started
|
122
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
|
123
|
+
state = :auth_sent
|
124
|
+
stream.receive_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"
|
125
|
+
true
|
126
|
+
|
127
|
+
when :auth_sent
|
128
|
+
val.must_match(/stream:stream/)
|
129
|
+
state = :complete
|
130
|
+
true
|
131
|
+
|
132
|
+
else
|
133
|
+
false
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
stream.connection_completed
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'will connect via SSL ANONYMOUS when asked' do
|
141
|
+
state = nil
|
142
|
+
client = mock()
|
143
|
+
client.stubs(:jid=)
|
144
|
+
stream = MockStream.new client, JID.new('n@d/r'), 'pass'
|
145
|
+
|
146
|
+
stream.expects(:send_data).times(3).with do |val|
|
147
|
+
case state
|
148
|
+
when nil
|
149
|
+
val.must_match(/stream:stream/)
|
150
|
+
state = :started
|
151
|
+
stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
|
152
|
+
true
|
153
|
+
|
154
|
+
when :started
|
155
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS">bg==</auth>')
|
156
|
+
state = :auth_sent
|
157
|
+
stream.receive_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"
|
158
|
+
true
|
159
|
+
|
160
|
+
when :auth_sent
|
161
|
+
val.must_match(/stream:stream/)
|
162
|
+
state = :complete
|
163
|
+
true
|
164
|
+
|
165
|
+
else
|
166
|
+
false
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
stream.connection_completed
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'will bind to a resource set by the server' do
|
174
|
+
state = nil
|
175
|
+
class Client; attr_accessor :jid; end
|
176
|
+
client = Client.new
|
177
|
+
|
178
|
+
jid = JID.new('n@d')
|
179
|
+
stream = MockStream.new client, jid, 'pass'
|
180
|
+
|
181
|
+
stream.expects(:send_data).times(2).with do |val|
|
182
|
+
case state
|
183
|
+
when nil
|
184
|
+
val.must_match(/stream:stream/)
|
185
|
+
state = :started
|
186
|
+
stream.receive_data "<stream:stream><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></stream:features>"
|
187
|
+
true
|
188
|
+
|
189
|
+
when :started
|
190
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s*/>})
|
191
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
192
|
+
state = :complete
|
193
|
+
stream.receive_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{jid}/server_resource</jid></bind></iq>"
|
194
|
+
client.jid.must_equal JID.new('n@d/server_resource')
|
195
|
+
true
|
196
|
+
|
197
|
+
else
|
198
|
+
false
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
stream.connection_completed
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'will bind to a resource set by the client' do
|
206
|
+
state = nil
|
207
|
+
class Client; attr_accessor :jid; end
|
208
|
+
client = Client.new
|
209
|
+
|
210
|
+
jid = JID.new('n@d/r')
|
211
|
+
stream = MockStream.new client, jid, 'pass'
|
212
|
+
|
213
|
+
stream.expects(:send_data).times(2).with do |val|
|
214
|
+
case state
|
215
|
+
when nil
|
216
|
+
val.must_match(/stream:stream/)
|
217
|
+
state = :started
|
218
|
+
stream.receive_data "<stream:stream><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></stream:features>"
|
219
|
+
true
|
220
|
+
|
221
|
+
when :started
|
222
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
|
223
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
224
|
+
state = :complete
|
225
|
+
stream.receive_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{jid}</jid></bind></iq>"
|
226
|
+
client.jid.must_equal JID.new('n@d/r')
|
227
|
+
true
|
228
|
+
|
229
|
+
else
|
230
|
+
false
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
stream.connection_completed
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'will establish a session if requested' do
|
238
|
+
state = nil
|
239
|
+
client = mock()
|
240
|
+
client.stubs(:jid=)
|
241
|
+
stream = MockStream.new client, JID.new('n@d/r'), 'pass'
|
242
|
+
|
243
|
+
stream.expects(:send_data).times(2).with do |val|
|
244
|
+
case state
|
245
|
+
when nil
|
246
|
+
val.must_match(/stream:stream/)
|
247
|
+
state = :started
|
248
|
+
stream.receive_data "<stream:stream><stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></stream:features>"
|
249
|
+
true
|
250
|
+
|
251
|
+
when :started
|
252
|
+
val.must_match('<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>')
|
253
|
+
state = :completed
|
254
|
+
true
|
255
|
+
|
256
|
+
else
|
257
|
+
false
|
258
|
+
|
259
|
+
end
|
260
|
+
end
|
261
|
+
stream.connection_completed
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::XMPPNode' do
|
4
|
+
it 'generates a new node' do
|
5
|
+
n = XMPPNode.new 'foo'
|
6
|
+
n.element_name.must_equal 'foo'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'generates a node based on the current name' do
|
10
|
+
class Foo < XMPPNode; end
|
11
|
+
Foo.name = 'foo'
|
12
|
+
Foo.new.element_name.must_equal 'foo'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets the namespace on creation' do
|
16
|
+
class Foo < XMPPNode; end
|
17
|
+
Foo.xmlns = 'foo'
|
18
|
+
Foo.new('foo').xmlns.must_equal 'foo'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'registers sub classes' do
|
22
|
+
class Foo < XMPPNode; register 'foo', 'foo:bar'; end
|
23
|
+
Foo.name.must_equal 'foo'
|
24
|
+
Foo.xmlns.must_equal 'foo:bar'
|
25
|
+
XMPPNode.class_from_registration('foo', 'foo:bar').must_equal Foo
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'imports another node' do
|
29
|
+
class Foo < XMPPNode; register 'foo', 'foo:bar'; end
|
30
|
+
n = XMPPNode.new('foo')
|
31
|
+
n.xmlns = 'foo:bar'
|
32
|
+
XMPPNode.import(n).must_be_kind_of Foo
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can convert itself into a stanza' do
|
36
|
+
class Foo < XMPPNode; register 'foo'; end
|
37
|
+
n = XMPPNode.new('foo')
|
38
|
+
n.to_stanza.must_be_kind_of Foo
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'provides "attr_accessor" for xmlns' do
|
42
|
+
n = XMPPNode.new('foo')
|
43
|
+
n.xmlns.must_be_nil
|
44
|
+
n['xmlns'].must_be_nil
|
45
|
+
|
46
|
+
n.xmlns = 'foo:bar'
|
47
|
+
n.xmlns.must_equal 'foo:bar'
|
48
|
+
n['xmlns'].must_equal 'foo:bar'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'will remove a child element' do
|
52
|
+
n = XMPPNode.new 'foo'
|
53
|
+
n << XMPPNode.new('bar')
|
54
|
+
n << XMPPNode.new('bar')
|
55
|
+
|
56
|
+
n.find('bar').size.must_equal 2
|
57
|
+
n.remove_child 'bar'
|
58
|
+
n.find('bar').size.must_equal 1
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'will remove a child with a specific xmlns' do
|
62
|
+
n = XMPPNode.new 'foo'
|
63
|
+
n << XMPPNode.new('bar')
|
64
|
+
c = XMPPNode.new('bar')
|
65
|
+
c.xmlns = 'foo:bar'
|
66
|
+
n << c
|
67
|
+
|
68
|
+
n.find('bar').size.must_equal 2
|
69
|
+
n.remove_child 'bar', 'foo:bar'
|
70
|
+
n.find('bar').size.must_equal 1
|
71
|
+
n.find('bar').first.xmlns.must_be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'will remove all child elements' do
|
75
|
+
n = XMPPNode.new 'foo'
|
76
|
+
n << XMPPNode.new('bar')
|
77
|
+
n << XMPPNode.new('bar')
|
78
|
+
|
79
|
+
n.find('bar').size.must_equal 2
|
80
|
+
n.remove_children 'bar'
|
81
|
+
n.find('bar').size.must_equal 0
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'provides a helper to grab content from a child' do
|
85
|
+
n = XMPPNode.new 'foo'
|
86
|
+
n << XMPPNode.new('bar', 'baz')
|
87
|
+
n.content_from(:bar).must_equal 'baz'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'provides a copy mechanism' do
|
91
|
+
n = XMPPNode.new 'foo'
|
92
|
+
n2 = n.copy
|
93
|
+
n2.object_id.wont_equal n.object_id
|
94
|
+
n2.element_name.must_equal n.element_name
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'provides an inhert mechanism' do
|
98
|
+
n = XMPPNode.new 'foo'
|
99
|
+
n2 = XMPPNode.new 'foo', 'bar'
|
100
|
+
n2['foo'] = 'bar'
|
101
|
+
|
102
|
+
n.inherit(n2)
|
103
|
+
n['foo'].must_equal 'bar'
|
104
|
+
n.content.must_equal 'bar'
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'provides a mechanism to inherit attrs' do
|
108
|
+
n = XMPPNode.new 'foo'
|
109
|
+
n2 = XMPPNode.new 'foo'
|
110
|
+
n2['foo'] = 'bar'
|
111
|
+
|
112
|
+
n.inherit_attrs(n2.attributes)
|
113
|
+
n['foo'].must_equal 'bar'
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'cuts line breaks out of #to_s' do
|
117
|
+
n = XMPPNode.new 'foo'
|
118
|
+
n << XMPPNode.new('bar', 'baz')
|
119
|
+
n.to_s.scan(">\n<").size.must_equal 0
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'overrides #find to find without xpath' do
|
123
|
+
n = XMPPNode.new 'foo'
|
124
|
+
n << XMPPNode.new('bar', 'baz')
|
125
|
+
n.find('bar').must_be_kind_of Array
|
126
|
+
|
127
|
+
XML::Document.new.root = n
|
128
|
+
n.find('bar').must_be_kind_of XML::XPath::Object
|
129
|
+
end
|
130
|
+
end
|