sprsquish-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.
@@ -0,0 +1,80 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::RosterItem' do
4
+ it 'initializes with JID' do
5
+ jid = JID.new(jid)
6
+ i = RosterItem.new jid
7
+ i.jid.must_equal jid
8
+ end
9
+
10
+ it 'initializes with an Iq::RosterItem' do
11
+ jid = 'n@d/r'
12
+ i = RosterItem.new Stanza::Iq::Roster::RosterItem.new(jid)
13
+ i.jid.must_equal JID.new(jid).stripped
14
+ end
15
+
16
+ it 'has a JID setter that strips the JID' do
17
+ jid = JID.new('n@d/r')
18
+ i = RosterItem.new nil
19
+ i.jid = jid
20
+ i.jid.must_equal jid.stripped
21
+ end
22
+
23
+ it 'has a subscription setter that forces a symbol' do
24
+ i = RosterItem.new nil
25
+ i.subscription = 'remove'
26
+ i.subscription.must_equal :remove
27
+ end
28
+
29
+ it 'forces the type of subscription' do
30
+ proc { RosterItem.new(nil).subscription = 'foo' }.must_raise Blather::ArgumentError
31
+ end
32
+
33
+ it 'returns :none if the subscription field is blank' do
34
+ RosterItem.new(nil).subscription.must_equal :none
35
+ end
36
+
37
+ it 'ensure #ask is a symbol' do
38
+ i = RosterItem.new(nil)
39
+ i.ask = 'subscribe'
40
+ i.ask.must_equal :subscribe
41
+ end
42
+
43
+ it 'forces #ask to be :subscribe or nothing at all' do
44
+ proc { RosterItem.new(nil).ask = 'foo' }.must_raise Blather::ArgumentError
45
+ end
46
+
47
+ it 'generates a stanza with #to_stanza' do
48
+ jid = JID.new('n@d/r')
49
+ i = RosterItem.new jid
50
+ s = i.to_stanza
51
+ s.must_be_kind_of Stanza::Iq::Roster
52
+ s.items.first.jid.must_equal jid.stripped
53
+ end
54
+
55
+ it 'returns status based on priority' do
56
+ setup_item_with_presences
57
+ @i.status.must_equal @p2
58
+ end
59
+
60
+ it 'returns status based on resource' do
61
+ setup_item_with_presences
62
+ @i.status('a').must_equal @p
63
+ end
64
+
65
+ def setup_item_with_presences
66
+ @jid = JID.new('n@d/r')
67
+ @i = RosterItem.new @jid
68
+
69
+ @p = Stanza::Presence::Status.new(:away)
70
+ @p.from = 'n@d/a'
71
+ @p.priority = 0
72
+
73
+ @p2 = Stanza::Presence::Status.new(:dnd)
74
+ @p2.from = 'n@d/b'
75
+ @p2.priority = -1
76
+
77
+ @i.status = @p
78
+ @i.status = @p2
79
+ end
80
+ end
@@ -0,0 +1,79 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::Roster' do
4
+ before do
5
+ @stream = mock()
6
+ @stream.stubs(:send_data)
7
+
8
+ @stanza = mock()
9
+ items = []; 4.times { |n| items << JID.new("n@d/#{n}r") }
10
+ @stanza.stubs(:items).returns(items)
11
+
12
+ @roster = Roster.new(@stream, @stanza)
13
+ end
14
+
15
+ it 'initializes with items' do
16
+ @roster.items.map { |_,i| i.jid.to_s }.must_equal(@stanza.items.map { |i| i.stripped.to_s }.uniq)
17
+ end
18
+
19
+ it 'processes @stanzas with remove requests' do
20
+ s = @roster['n@d/0r']
21
+ s.subscription = :remove
22
+ proc { @roster.process(s.to_stanza) }.must_change('@roster.items', :length, :by => -1)
23
+ end
24
+
25
+ it 'processes @stanzas with add requests' do
26
+ s = Stanza::Iq::Roster::RosterItem.new('a@b/c').to_stanza
27
+ proc { @roster.process(s) }.must_change('@roster.items', :length, :by => 1)
28
+ end
29
+
30
+ it 'allows a jid to be pushed' do
31
+ jid = 'a@b/c'
32
+ proc { @roster.push(jid) }.must_change('@roster.items', :length, :by => 1)
33
+ @roster[jid].wont_be_nil
34
+ end
35
+
36
+ it 'allows an item to be pushed' do
37
+ jid = 'a@b/c'
38
+ item = RosterItem.new(JID.new(jid))
39
+ proc { @roster.push(item) }.must_change('@roster.items', :length, :by => 1)
40
+ @roster[jid].wont_be_nil
41
+ end
42
+
43
+ it 'sends a @roster addition over the wire' do
44
+ stream = mock()
45
+ stream.expects(:send_data)
46
+ roster = Roster.new stream, @stanza
47
+ roster.push('a@b/c')
48
+ end
49
+
50
+ it 'removes a JID' do
51
+ proc { @roster.delete 'n@d' }.must_change('@roster.items', :length, :by => -1)
52
+ end
53
+
54
+ it 'sends a @roster removal over the wire' do
55
+ stream = mock(:send_data => nil)
56
+ roster = Roster.new stream, @stanza
57
+ roster.delete('a@b/c')
58
+ end
59
+
60
+ it 'returns an item through []' do
61
+ item = @roster['n@d']
62
+ item.must_be_kind_of RosterItem
63
+ item.jid.must_equal JID.new('n@d')
64
+ end
65
+
66
+ it 'responds to #each' do
67
+ @roster.must_respond_to :each
68
+ end
69
+
70
+ it 'cycles through the items using #each' do
71
+ @roster.map { |i| i }.sort.must_equal(@roster.items.sort)
72
+ end
73
+
74
+ it 'returns a duplicate of items through #items' do
75
+ items = @roster.items
76
+ items.delete 'n@d'
77
+ items.wont_equal @roster.items
78
+ end
79
+ end
@@ -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
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. lib blather])
2
+ require 'rubygems'
3
+ require 'minitest/spec'
4
+ require 'mocha'
5
+
6
+ module MiniTest
7
+ if MINI_DIR =~ %r{^./}
8
+ require 'pathname'
9
+ path = Pathname.new(MINI_DIR).realpath
10
+ # remove_const 'MINI_DIR'
11
+ # const_set 'MINI_DIR', path.to_s
12
+ end
13
+
14
+ module Assertions
15
+ def assert_change(obj, method, args = {}, msg = nil)
16
+ msg ||= proc {
17
+ m = "Expected #{obj}.#{method} to change"
18
+ m << " by #{mu_pp args[:by]}" if args[:by]
19
+ m << (args[:from] ? " from #{mu_pp args[:from]}" : '') + " to #{mu_pp args[:to]}" if args[:to]
20
+ m
21
+ }.call
22
+
23
+ init_val = eval(obj).__send__(method)
24
+ yield
25
+ new_val = eval(obj).__send__(method)
26
+
27
+ assert_equal(args[:by], (new_val - init_val), msg) if args[:by]
28
+ assert_equal([args[:from], args[:to]], [(init_val if args[:from]), new_val], msg) if args[:to]
29
+ refute_equal(init_val, new_val, msg) if args.empty?
30
+ end
31
+ end
32
+ end
33
+
34
+ class Object
35
+ def must_change *args, &block
36
+ return MiniTest::Spec.current.assert_change(*args, &self) if Proc === self
37
+ return MiniTest::Spec.current.assert_change(args.first, self) if args.size == 1
38
+ return MiniTest::Spec.current.assert_change(self, *args)
39
+ end
40
+ end
41
+
42
+ require 'mocha/expectation_error'
43
+
44
+ include Blather
45
+ include MiniTest
46
+
47
+ LOG.level = Logger::INFO
48
+
49
+ Unit.autorun