shingara-blather 0.4.8

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