shingara-blather 0.4.8

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.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. metadata +249 -0
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe Blather::Stream::Component do
4
+ class MockServer; end
5
+ module ServerMock
6
+ def receive_data(data)
7
+ @server ||= MockServer.new
8
+ @server.receive_data data, self
9
+ end
10
+ end
11
+
12
+ def mocked_server(times = nil, &block)
13
+ @client ||= mock()
14
+ @client.stubs(:unbind) unless @client.respond_to?(:unbind)
15
+ @client.stubs(:jid=) unless @client.respond_to?(:jid=)
16
+
17
+ MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
18
+ EventMachine::run {
19
+ # Mocked server
20
+ EventMachine::start_server '127.0.0.1', 12345, ServerMock
21
+
22
+ # Blather::Stream connection
23
+ EM.connect('127.0.0.1', 12345, Blather::Stream::Component, @client, @jid || 'comp.id', 'secret') { |c| @stream = c }
24
+ }
25
+ end
26
+
27
+ it 'can be started' do
28
+ client = mock()
29
+ params = [client, 'comp.id', 'secret', 'host', 1234]
30
+ EM.expects(:connect).with do |*parms|
31
+ parms[0] == 'host' &&
32
+ parms[1] == 1234 &&
33
+ parms[3] == client &&
34
+ parms[4] == 'comp.id'
35
+ end
36
+
37
+ Blather::Stream::Component.start *params
38
+ end
39
+
40
+ it 'shakes hands with the server' do
41
+ state = nil
42
+ mocked_server(2) do |val, server|
43
+ case state
44
+ when nil
45
+ state = :started
46
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' id='12345'>"
47
+ val.must_match(/stream:stream/)
48
+
49
+ when :started
50
+ server.send_data '<handshake/>'
51
+ EM.stop
52
+ val.must_equal "<handshake>#{Digest::SHA1.hexdigest('12345'+"secret")}</handshake>"
53
+
54
+ end
55
+ end
56
+ end
57
+
58
+ it 'starts the stream once the connection is complete' do
59
+ mocked_server(1) { |val, _| EM.stop; val.must_match(/stream:stream/) }
60
+ end
61
+
62
+ it 'sends stanzas to the client when the stream is ready' do
63
+ @client = mock(:post_init)
64
+ @client.expects(:receive_data).with do |n|
65
+ EM.stop
66
+ n.kind_of? Blather::XMPPNode
67
+ end
68
+
69
+ state = nil
70
+ mocked_server(2) do |val, server|
71
+ case state
72
+ when nil
73
+ state = :started
74
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' id='12345'>"
75
+ val.must_match(/stream:stream/)
76
+
77
+ when :started
78
+ server.send_data '<handshake/>'
79
+ server.send_data "<message to='comp.id' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
80
+ val.must_equal "<handshake>#{Digest::SHA1.hexdigest('12345'+"secret")}</handshake>"
81
+
82
+ end
83
+ end
84
+ end
85
+
86
+ it 'sends stanzas to the wire ensuring "from" is set' do
87
+ client = mock()
88
+ client.stubs(:jid)
89
+ client.stubs(:jid=)
90
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
91
+ comp = Blather::Stream::Component.new nil, client, 'jid.com', 'pass'
92
+ comp.expects(:send_data).with { |s| s.must_match(/^<message[^>]*from="jid\.com"/) }
93
+ comp.send msg
94
+ end
95
+ end
@@ -0,0 +1,145 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe Blather::Stream::Parser do
4
+ before do
5
+ @client = Class.new do
6
+ attr_reader :data
7
+ def stopped?; false; end
8
+ def receive(data)
9
+ @data ||= []
10
+ @data << data
11
+ end
12
+ end.new
13
+ @parser = Blather::Stream::Parser.new @client
14
+ end
15
+
16
+ after { @client = @parser = nil}
17
+
18
+ def check_parse(data)
19
+ @parser.receive_data data
20
+ @client.data.size.must_equal 1
21
+ @client.data[0].to_s.gsub(/\n\s*/,'').must_equal data
22
+ end
23
+
24
+ it 'handles fragmented parsing' do
25
+ @parser.receive_data '<foo>'
26
+ @parser.receive_data '<bar/>'
27
+ @parser.receive_data '</foo>'
28
+ @client.data.size.must_equal 1
29
+ @client.data[0].to_s.gsub(/\n\s*/,'').must_equal '<foo><bar/></foo>'
30
+ end
31
+
32
+ it 'handles a basic example' do
33
+ check_parse "<foo/>"
34
+ end
35
+
36
+ it 'handles a basic namespace definition' do
37
+ check_parse '<foo xmlns="bar:baz"/>'
38
+ end
39
+
40
+ it 'handles multiple namespace definitions' do
41
+ check_parse '<foo xmlns="bar:baz" xmlns:bar="baz"/>'
42
+ end
43
+
44
+ it 'handles prefix namespacing' do
45
+ check_parse '<bar:foo xmlns="bar:baz" xmlns:bar="baz"/>'
46
+ end
47
+
48
+ it 'handles namespaces with children' do
49
+ check_parse "<foo xmlns=\"bar:baz\"><bar/></foo>"
50
+ end
51
+
52
+ it 'handles multiple namespaces with children' do
53
+ check_parse "<foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar/></foo>"
54
+ end
55
+
56
+ it 'handles prefix namespaces with children' do
57
+ check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar/></bar:foo>"
58
+ end
59
+
60
+ it 'handles prefix namespaces with children in the namespace' do
61
+ check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar:bar/></bar:foo>"
62
+ end
63
+
64
+ it 'handles prefix namespaces with children in the namespace' do
65
+ check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><baz><bar:buz/></baz></bar:foo>"
66
+ end
67
+
68
+ it 'handles attributes' do
69
+ check_parse '<foo bar="baz"/>'
70
+ end
71
+
72
+ it 'handles character input' do
73
+ check_parse '<foo>bar baz fizbuz</foo>'
74
+ end
75
+
76
+ it 'handles a complex input' do
77
+ data = [
78
+ '<message type="error" id="another-id">',
79
+ '<error type="modify">',
80
+ '<undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>',
81
+ '<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Some special application diagnostic information...</text>',
82
+ '<special-application-condition xmlns="special:application-ns"/>',
83
+ "</error>",
84
+ "</message>",
85
+ ]
86
+ data.each { |d| @parser.receive_data d }
87
+ @client.data.size.must_equal 1
88
+ @client.data[0].to_s.split("\n").map{|n|n.strip}.must_equal data
89
+ @client.data[0].xpath('//*[namespace-uri()="urn:ietf:params:xml:ns:xmpp-stanzas"]').size.must_equal 2
90
+ end
91
+
92
+ it 'responds with stream:stream as a separate response' do
93
+ data = [
94
+ '<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" to="example.com" version="1.0">',
95
+ '<foo/>'
96
+ ]
97
+ data.each { |d| @parser.receive_data d }
98
+ @client.data.size.must_equal 2
99
+ @client.data[0].document.xpath('/stream:stream[@to="example.com" and @version="1.0"]', 'xmlns' => 'jabber:client', 'stream' => 'http://etherx.jabber.org/streams').size.must_equal 1
100
+ @client.data[1].to_s.must_equal '<foo/>'
101
+ end
102
+
103
+ it 'response with stream:end when receiving </stream:stream>' do
104
+ @parser.receive_data '<stream:stream xmlns:stream="http://etherx.jabber.org/streams"/>'
105
+ @client.data.size.must_equal 2
106
+ @client.data[1].to_s.must_equal '<stream:end xmlns:stream="http://etherx.jabber.org/streams"/>'
107
+ end
108
+
109
+ it 'raises ParseError when an error is sent' do
110
+ lambda { @parser.receive_data "<stream:stream>" }.must_raise(Blather::ParseError)
111
+ end
112
+
113
+ it 'handles stream stanzas without an issue' do
114
+ data = [
115
+ '<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" to="example.com" version="1.0">',
116
+ '<stream:features/>'
117
+ ]
118
+ data.each { |d| @parser.receive_data d }
119
+ @client.data.size.must_equal 2
120
+ @client.data[0].document.xpath('/stream:stream[@to="example.com" and @version="1.0"]', 'xmlns' => 'jabber:client', 'stream' => 'http://etherx.jabber.org/streams').size.must_equal 1
121
+ @client.data[1].to_s.must_equal '<stream:features xmlns:stream="http://etherx.jabber.org/streams"/>'
122
+ end
123
+
124
+ it 'ignores the client namespace on stanzas' do
125
+ [ "<message type='chat' to='n@d' from='n@d/r' id='id1' xmlns='jabber:client'>",
126
+ "<body>exit</body>",
127
+ "<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>exit</body></html>",
128
+ "</message>"
129
+ ].each { |d| @parser.receive_data d }
130
+ @client.data.size.must_equal 1
131
+ @client.data[0].document.xpath('/message/body[.="exit"]').wont_be_empty
132
+ @client.data[0].document.xpath('/message/im:html/xhtml:body[.="exit"]', 'im' => 'http://jabber.org/protocol/xhtml-im', 'xhtml' => 'http://www.w3.org/1999/xhtml').wont_be_empty
133
+ end
134
+
135
+ it 'ignores the component namespace on stanzas' do
136
+ [ "<message type='chat' to='n@d' from='n@d/r' id='id1' xmlns='jabber:component:accept'>",
137
+ "<body>exit</body>",
138
+ "<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>exit</body></html>",
139
+ "</message>"
140
+ ].each { |d| @parser.receive_data d }
141
+ @client.data.size.must_equal 1
142
+ @client.data[0].document.xpath('/message/body[.="exit"]').wont_be_empty
143
+ @client.data[0].document.xpath('/message/im:html/xhtml:body[.="exit"]', 'im' => 'http://jabber.org/protocol/xhtml-im', 'xhtml' => 'http://www.w3.org/1999/xhtml').wont_be_empty
144
+ end
145
+ end
@@ -0,0 +1,231 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe Blather::XMPPNode do
4
+ before { @doc = Nokogiri::XML::Document.new }
5
+
6
+ it 'generates a new node automatically setting the document' do
7
+ n = Blather::XMPPNode.new 'foo'
8
+ n.element_name.must_equal 'foo'
9
+ n.document.wont_equal @doc
10
+ end
11
+
12
+ it 'sets the new document root to the node' do
13
+ n = Blather::XMPPNode.new 'foo'
14
+ n.document.root.must_equal n
15
+ end
16
+
17
+ it 'does not set the document root if the document is provided' do
18
+ n = Blather::XMPPNode.new 'foo', @doc
19
+ n.document.root.wont_equal n
20
+ end
21
+
22
+ it 'generates a new node with the given document' do
23
+ n = Blather::XMPPNode.new 'foo', @doc
24
+ n.element_name.must_equal 'foo'
25
+ n.document.must_equal @doc
26
+ end
27
+
28
+ it 'generates a node based on the registered_name' do
29
+ foo = Class.new(Blather::XMPPNode)
30
+ foo.registered_name = 'foo'
31
+ foo.new.element_name.must_equal 'foo'
32
+ end
33
+
34
+ it 'sets the namespace on creation' do
35
+ foo = Class.new(Blather::XMPPNode)
36
+ foo.registered_ns = 'foo'
37
+ foo.new('foo').namespace.href.must_equal 'foo'
38
+ end
39
+
40
+ it 'registers sub classes' do
41
+ class RegistersSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end
42
+ RegistersSubClass.registered_name.must_equal 'foo'
43
+ RegistersSubClass.registered_ns.must_equal 'foo:bar'
44
+ Blather::XMPPNode.class_from_registration('foo', 'foo:bar').must_equal RegistersSubClass
45
+ end
46
+
47
+ it 'imports another node' do
48
+ class ImportSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end
49
+ n = Blather::XMPPNode.new('foo')
50
+ n.namespace = 'foo:bar'
51
+ Blather::XMPPNode.import(n).must_be_kind_of ImportSubClass
52
+ end
53
+
54
+ it 'provides an attribute reader' do
55
+ foo = Blather::XMPPNode.new
56
+ foo.read_attr(:bar).must_be_nil
57
+ foo[:bar] = 'baz'
58
+ foo.read_attr(:bar).must_equal 'baz'
59
+ end
60
+
61
+ it 'provides an attribute reader with converstion' do
62
+ foo = Blather::XMPPNode.new
63
+ foo.read_attr(:bar, :to_sym).must_be_nil
64
+ foo[:bar] = 'baz'
65
+ foo.read_attr(:bar, :to_sym).must_equal :baz
66
+ end
67
+
68
+ it 'provides an attribute writer' do
69
+ foo = Blather::XMPPNode.new
70
+ foo[:bar].must_be_nil
71
+ foo.write_attr(:bar, 'baz')
72
+ foo[:bar].must_equal 'baz'
73
+ end
74
+
75
+ it 'provides a content reader' do
76
+ foo = Blather::XMPPNode.new('foo')
77
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
78
+ bar.content = 'baz'
79
+ foo.read_content(:bar).must_equal 'baz'
80
+ end
81
+
82
+ it 'provides a content reader that converts the value' do
83
+ foo = Blather::XMPPNode.new('foo')
84
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
85
+ bar.content = 'baz'
86
+ foo.read_content(:bar, :to_sym).must_equal :baz
87
+ end
88
+
89
+ it 'provides a content writer' do
90
+ foo = Blather::XMPPNode.new('foo')
91
+ foo.set_content_for :bar, 'baz'
92
+ foo.content_from(:bar).must_equal 'baz'
93
+ end
94
+
95
+ it 'provides a content writer that removes a child when set to nil' do
96
+ foo = Blather::XMPPNode.new('foo')
97
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
98
+ bar.content = 'baz'
99
+ foo.content_from(:bar).must_equal 'baz'
100
+ foo.xpath('bar').wont_be_empty
101
+
102
+ foo.set_content_for :bar, nil
103
+ foo.content_from(:bar).must_be_nil
104
+ foo.xpath('bar').must_be_empty
105
+ end
106
+
107
+ it 'can convert itself into a stanza' do
108
+ class StanzaConvert < Blather::XMPPNode; register 'foo'; end
109
+ n = Blather::XMPPNode.new('foo')
110
+ n.to_stanza.must_be_kind_of StanzaConvert
111
+ end
112
+
113
+ it 'provides "attr_accessor" for namespace' do
114
+ n = Blather::XMPPNode.new('foo')
115
+ n.namespace.must_be_nil
116
+
117
+ n.namespace = 'foo:bar'
118
+ n.namespace_href.must_equal 'foo:bar'
119
+ end
120
+
121
+ it 'will remove a child element' do
122
+ n = Blather::XMPPNode.new 'foo'
123
+ n << Blather::XMPPNode.new('bar', n.document)
124
+ n << Blather::XMPPNode.new('bar', n.document)
125
+
126
+ n.find(:bar).size.must_equal 2
127
+ n.remove_child 'bar'
128
+ n.find(:bar).size.must_equal 1
129
+ end
130
+
131
+ it 'will remove a child with a specific xmlns' do
132
+ n = Blather::XMPPNode.new 'foo'
133
+ n << Blather::XMPPNode.new('bar')
134
+ c = Blather::XMPPNode.new('bar')
135
+ c.namespace = 'foo:bar'
136
+ n << c
137
+
138
+ n.find(:bar).size.must_equal 2
139
+ n.remove_child 'bar', 'foo:bar'
140
+ n.find(:bar).size.must_equal 1
141
+ n.find(:bar).first.namespace.must_be_nil
142
+ end
143
+
144
+ it 'will remove all child elements' do
145
+ n = Blather::XMPPNode.new 'foo'
146
+ n << Blather::XMPPNode.new('bar')
147
+ n << Blather::XMPPNode.new('bar')
148
+
149
+ n.find(:bar).size.must_equal 2
150
+ n.remove_children 'bar'
151
+ n.find(:bar).size.must_equal 0
152
+ end
153
+
154
+ it 'provides a copy mechanism' do
155
+ n = Blather::XMPPNode.new 'foo'
156
+ n2 = n.copy
157
+ n2.object_id.wont_equal n.object_id
158
+ n2.element_name.must_equal n.element_name
159
+ end
160
+
161
+ it 'provides an inherit mechanism' do
162
+ n = Blather::XMPPNode.new 'foo'
163
+ n2 = Blather::XMPPNode.new 'foo'
164
+ n2.content = 'bar'
165
+ n2['foo'] = 'bar'
166
+
167
+ n.inherit(n2)
168
+ n['foo'].must_equal 'bar'
169
+ n.content.must_equal 'bar'
170
+ n2.to_s.must_equal n.to_s
171
+ end
172
+
173
+ it 'holds on to namespaces when inheriting content' do
174
+ n = parse_stanza('<message><bar:foo xmlns:bar="http://bar.com"></message>').root
175
+ n2 = Blather::XMPPNode.new('message').inherit n
176
+ n2.to_s.must_equal n.to_s
177
+ end
178
+
179
+ it 'provides a mechanism to inherit attrs' do
180
+ n = Blather::XMPPNode.new 'foo'
181
+ n2 = Blather::XMPPNode.new 'foo'
182
+ n2['foo'] = 'bar'
183
+
184
+ n.inherit_attrs(n2.attributes)
185
+ n['foo'].must_equal 'bar'
186
+ end
187
+
188
+ it 'has a content_from helper that pulls the content from a child node' do
189
+ f = Blather::XMPPNode.new('foo')
190
+ f << (b = Blather::XMPPNode.new('bar'))
191
+ b.content = 'content'
192
+ f.content_from(:bar).must_equal 'content'
193
+ end
194
+
195
+ it 'returns nil when sent #content_from and a missing node' do
196
+ f = Blather::XMPPNode.new('foo')
197
+ f.content_from(:bar).must_be_nil
198
+ end
199
+
200
+ it 'creates a new node and sets content when sent #set_content_for' do
201
+ f = Blather::XMPPNode.new('foo')
202
+ f.must_respond_to :set_content_for
203
+ f.xpath('bar').must_be_empty
204
+ f.set_content_for :bar, :baz
205
+ f.xpath('bar').wont_be_empty
206
+ f.xpath('bar').first.content.must_equal 'baz'
207
+ end
208
+
209
+ it 'removes a child node when sent #set_content_for with nil' do
210
+ f = Blather::XMPPNode.new('foo')
211
+ f << (b = Blather::XMPPNode.new('bar'))
212
+ f.must_respond_to :set_content_for
213
+ f.xpath('bar').wont_be_empty
214
+ f.set_content_for :bar, nil
215
+ f.xpath('bar').must_be_empty
216
+ end
217
+
218
+ it 'will change the content of an existing node when sent #set_content_for' do
219
+ f = Blather::XMPPNode.new('foo')
220
+ f << (b = Blather::XMPPNode.new('bar'))
221
+ b.content = 'baz'
222
+ f.must_respond_to :set_content_for
223
+ f.xpath('bar').wont_be_empty
224
+ f.xpath('bar').first.content.must_equal 'baz'
225
+ control = f.xpath('bar').first.pointer_id
226
+
227
+ f.set_content_for :bar, 'fiz'
228
+ f.xpath('bar').first.content.must_equal 'fiz'
229
+ f.xpath('bar').first.pointer_id.must_equal control
230
+ end
231
+ end