blather 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +41 -12
  3. data/examples/echo.rb +1 -1
  4. data/examples/execute.rb +0 -5
  5. data/examples/pubsub/cli.rb +64 -0
  6. data/examples/pubsub/ping_pong.rb +18 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/xmpp4r/echo.rb +35 -0
  9. data/lib/blather.rb +35 -12
  10. data/lib/blather/client.rb +1 -1
  11. data/lib/blather/client/client.rb +19 -13
  12. data/lib/blather/client/dsl.rb +16 -0
  13. data/lib/blather/client/dsl/pubsub.rb +133 -0
  14. data/lib/blather/core_ext/active_support.rb +1 -117
  15. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
  16. data/lib/blather/core_ext/nokogiri.rb +35 -0
  17. data/lib/blather/errors.rb +3 -20
  18. data/lib/blather/errors/sasl_error.rb +3 -1
  19. data/lib/blather/errors/stanza_error.rb +10 -17
  20. data/lib/blather/errors/stream_error.rb +11 -14
  21. data/lib/blather/jid.rb +1 -0
  22. data/lib/blather/roster.rb +9 -0
  23. data/lib/blather/roster_item.rb +6 -1
  24. data/lib/blather/stanza.rb +35 -18
  25. data/lib/blather/stanza/disco.rb +7 -1
  26. data/lib/blather/stanza/disco/disco_info.rb +45 -33
  27. data/lib/blather/stanza/disco/disco_items.rb +32 -21
  28. data/lib/blather/stanza/iq.rb +13 -8
  29. data/lib/blather/stanza/iq/query.rb +16 -8
  30. data/lib/blather/stanza/iq/roster.rb +33 -22
  31. data/lib/blather/stanza/message.rb +20 -31
  32. data/lib/blather/stanza/presence.rb +3 -5
  33. data/lib/blather/stanza/presence/status.rb +13 -21
  34. data/lib/blather/stanza/presence/subscription.rb +11 -16
  35. data/lib/blather/stanza/pubsub.rb +63 -0
  36. data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
  37. data/lib/blather/stanza/pubsub/create.rb +43 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +77 -0
  40. data/lib/blather/stanza/pubsub/items.rb +63 -0
  41. data/lib/blather/stanza/pubsub/publish.rb +58 -0
  42. data/lib/blather/stanza/pubsub/retract.rb +53 -0
  43. data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
  44. data/lib/blather/stanza/pubsub/subscription.rb +66 -0
  45. data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
  46. data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
  47. data/lib/blather/stanza/pubsub_owner.rb +41 -0
  48. data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
  49. data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
  50. data/lib/blather/stream.rb +76 -168
  51. data/lib/blather/stream/client.rb +1 -2
  52. data/lib/blather/stream/component.rb +9 -5
  53. data/lib/blather/stream/features.rb +53 -0
  54. data/lib/blather/stream/features/resource.rb +63 -0
  55. data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
  56. data/lib/blather/stream/features/session.rb +44 -0
  57. data/lib/blather/stream/features/tls.rb +28 -0
  58. data/lib/blather/stream/parser.rb +70 -46
  59. data/lib/blather/xmpp_node.rb +113 -52
  60. data/spec/blather/client/client_spec.rb +44 -58
  61. data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
  62. data/spec/blather/client/dsl_spec.rb +19 -6
  63. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  64. data/spec/blather/errors/sasl_error_spec.rb +8 -8
  65. data/spec/blather/errors/stanza_error_spec.rb +25 -33
  66. data/spec/blather/errors/stream_error_spec.rb +21 -16
  67. data/spec/blather/errors_spec.rb +4 -11
  68. data/spec/blather/jid_spec.rb +31 -30
  69. data/spec/blather/roster_item_spec.rb +34 -23
  70. data/spec/blather/roster_spec.rb +27 -12
  71. data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
  72. data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
  73. data/spec/blather/stanza/iq/query_spec.rb +34 -11
  74. data/spec/blather/stanza/iq/roster_spec.rb +47 -30
  75. data/spec/blather/stanza/iq_spec.rb +19 -14
  76. data/spec/blather/stanza/message_spec.rb +30 -17
  77. data/spec/blather/stanza/presence/status_spec.rb +43 -20
  78. data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
  79. data/spec/blather/stanza/presence_spec.rb +34 -21
  80. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  81. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  82. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  83. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  84. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  85. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  86. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  87. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  88. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  89. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  90. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  92. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  93. data/spec/blather/stanza/pubsub_spec.rb +62 -0
  94. data/spec/blather/stanza_spec.rb +53 -38
  95. data/spec/blather/stream/client_spec.rb +231 -88
  96. data/spec/blather/stream/component_spec.rb +14 -5
  97. data/spec/blather/stream/parser_spec.rb +145 -0
  98. data/spec/blather/xmpp_node_spec.rb +192 -96
  99. data/spec/fixtures/pubsub.rb +311 -0
  100. data/spec/spec_helper.rb +5 -4
  101. metadata +54 -18
  102. data/Rakefile +0 -139
  103. data/ext/extconf.rb +0 -65
  104. data/ext/push_parser.c +0 -209
  105. data/lib/blather/core_ext/libxml.rb +0 -28
  106. data/lib/blather/stream/resource.rb +0 -48
  107. data/lib/blather/stream/session.rb +0 -36
  108. data/lib/blather/stream/stream_handler.rb +0 -39
  109. data/lib/blather/stream/tls.rb +0 -33
  110. data/spec/blather/core_ext/libxml_spec.rb +0 -58
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
2
 
3
- describe 'Blather::Stream::Component' do
3
+ describe Blather::Stream::Component do
4
4
  class MockServer; end
5
5
  module ServerMock
6
6
  def receive_data(data)
@@ -19,8 +19,8 @@ describe 'Blather::Stream::Component' do
19
19
  # Mocked server
20
20
  EventMachine::start_server '127.0.0.1', 12345, ServerMock
21
21
 
22
- # Stream connection
23
- EM.connect('127.0.0.1', 12345, Stream::Component, @client, @jid || 'comp.id', 'secret') { |c| @stream = c }
22
+ # Blather::Stream connection
23
+ EM.connect('127.0.0.1', 12345, Blather::Stream::Component, @client, @jid || 'comp.id', 'secret') { |c| @stream = c }
24
24
  }
25
25
  end
26
26
 
@@ -34,7 +34,7 @@ describe 'Blather::Stream::Component' do
34
34
  parms[4] == 'comp.id'
35
35
  end
36
36
 
37
- Stream::Component.start *params
37
+ Blather::Stream::Component.start *params
38
38
  end
39
39
 
40
40
  it 'shakes hands with the server' do
@@ -63,7 +63,7 @@ describe 'Blather::Stream::Component' do
63
63
  @client = mock(:post_init)
64
64
  @client.expects(:receive_data).with do |n|
65
65
  EM.stop
66
- n.kind_of?(Stanza::Message) && @stream.ready?.must_equal(true)
66
+ n.kind_of? Blather::XMPPNode
67
67
  end
68
68
 
69
69
  state = nil
@@ -83,4 +83,13 @@ describe 'Blather::Stream::Component' do
83
83
  end
84
84
  end
85
85
 
86
+ it 'sends stanzas to the wire with 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
86
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="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
@@ -1,104 +1,182 @@
1
1
  require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
- describe 'Blather::XMPPNode' do
4
- it 'generates a new node' do
5
- n = XMPPNode.new 'foo'
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'
6
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
7
15
  end
8
16
 
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'
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'
13
32
  end
14
33
 
15
34
  it 'sets the namespace on creation' do
16
- class Foo < XMPPNode; end
17
- Foo.ns = 'foo'
18
- Foo.new('foo').namespace.must_equal 'foo'
35
+ foo = Class.new(Blather::XMPPNode)
36
+ foo.registered_ns = 'foo'
37
+ foo.new('foo').namespace.href.must_equal 'foo'
19
38
  end
20
39
 
21
40
  it 'registers sub classes' do
22
- class Foo < XMPPNode; register 'foo', 'foo:bar'; end
23
- Foo.name.must_equal 'foo'
24
- Foo.ns.must_equal 'foo:bar'
25
- XMPPNode.class_from_registration('foo', 'foo:bar').must_equal Foo
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
26
45
  end
27
46
 
28
47
  it 'imports another node' do
29
- class Foo < XMPPNode; register 'foo', 'foo:bar'; end
30
- n = XMPPNode.new('foo')
48
+ class ImportSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end
49
+ n = Blather::XMPPNode.new('foo')
31
50
  n.namespace = 'foo:bar'
32
- XMPPNode.import(n).must_be_kind_of Foo
51
+ Blather::XMPPNode.import(n).must_be_kind_of ImportSubClass
33
52
  end
34
53
 
35
54
  it 'provides an attribute_reader' do
36
- class Foo < XMPPNode
37
- attribute_reader :foo
38
- end
39
- f = Foo.new
40
- f.must_respond_to :foo
41
- f.foo.must_be_nil
42
- f.attributes[:foo] = 'bar'
43
- f.foo.must_equal :bar
44
- end
45
-
46
- it 'provides an attribute_reader and not convert to syms' do
47
- class Foo < XMPPNode
48
- attribute_reader :foo, :to_sym => false
49
- end
50
- f = Foo.new
51
- f.must_respond_to :foo
52
- f.foo.must_be_nil
53
- f.attributes[:foo] = 'bar'
54
- f.foo.must_equal 'bar'
55
+ foo = Class.new(Blather::XMPPNode) { attribute_reader :bar }.new
56
+ foo.must_respond_to :bar
57
+ foo.bar.must_be_nil
58
+ foo[:bar] = 'baz'
59
+ foo.bar.must_equal 'baz'
60
+ end
61
+
62
+ it 'provides an attribute_reader with converstion' do
63
+ foo = Class.new(Blather::XMPPNode) { attribute_reader :bar, :call => :to_sym }.new
64
+ foo.must_respond_to :bar
65
+ foo.bar.must_be_nil
66
+ foo[:bar] = 'baz'
67
+ foo.bar.must_equal :baz
55
68
  end
56
69
 
57
70
  it 'provides an attribute_writer' do
58
- class Foo < XMPPNode
59
- attribute_writer :foo
60
- end
61
- f = Foo.new
62
- f.attributes[:foo].must_be_nil
63
- f.foo = 'bar'
64
- f.attributes[:foo].must_equal 'bar'
71
+ foo = Class.new(Blather::XMPPNode) { attribute_writer :bar }.new
72
+ foo[:bar].must_be_nil
73
+ foo.bar = 'baz'
74
+ foo[:bar].must_equal 'baz'
65
75
  end
66
76
 
67
77
  it 'provides an attribute_accessor' do
68
- class Foo < XMPPNode
69
- attribute_accessor :foo
70
- attribute_accessor :bar, :to_sym => false
71
- end
72
- f = Foo.new
73
- f.must_respond_to :foo
74
- f.foo.must_be_nil
75
- f.foo = 'bar'
76
- f.foo.must_equal :bar
77
-
78
- f.must_respond_to :bar
79
- f.bar.must_be_nil
80
- f.bar = 'baz'
81
- f.bar.must_equal 'baz'
78
+ foo = Class.new(Blather::XMPPNode) do
79
+ attribute_accessor :bar, :call => :to_sym
80
+ attribute_accessor :baz
81
+ end.new
82
+ foo.must_respond_to :bar
83
+ foo.bar.must_be_nil
84
+ foo.bar = 'fiz'
85
+ foo.bar.must_equal :fiz
86
+
87
+ foo.must_respond_to :baz
88
+ foo.baz.must_be_nil
89
+ foo.baz = 'buz'
90
+ foo.baz.must_equal 'buz'
91
+ end
92
+
93
+ it 'provides a content reader' do
94
+ foo = Class.new(Blather::XMPPNode) { content_attr_reader :bar }.new('foo')
95
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
96
+ bar.content = 'baz'
97
+ foo.must_respond_to :bar
98
+ foo.bar.must_equal 'baz'
99
+ end
100
+
101
+ it 'provides a content reader that converts the value' do
102
+ foo = Class.new(Blather::XMPPNode) { content_attr_reader :bar, :to_sym }.new('foo')
103
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
104
+ bar.content = 'baz'
105
+ foo.must_respond_to :bar
106
+ foo.bar.must_equal :baz
107
+ end
108
+
109
+ it 'provides a content reader with a different node' do
110
+ foo = Class.new(Blather::XMPPNode) { content_attr_reader :bar, nil, :fiz }.new('foo')
111
+ foo << (fiz = Blather::XMPPNode.new('fiz', foo.document))
112
+ fiz.content = 'baz'
113
+ foo.must_respond_to :bar
114
+ foo.bar.must_equal 'baz'
115
+ end
116
+
117
+ it 'provides a content writer' do
118
+ foo = Class.new(Blather::XMPPNode) { content_attr_writer :bar }.new('foo')
119
+ foo.must_respond_to :bar=
120
+ foo.bar = 'baz'
121
+ foo.content_from(:bar).must_equal 'baz'
122
+ end
123
+
124
+ it 'provides a content writer with a different node' do
125
+ foo = Class.new(Blather::XMPPNode) { content_attr_writer :bar, :fiz }.new('foo')
126
+ foo.must_respond_to :bar=
127
+ foo.bar = 'baz'
128
+ foo.content_from(:fiz).must_equal 'baz'
129
+ end
130
+
131
+ it 'provides a content accessor' do
132
+ foo = Class.new(Blather::XMPPNode) { content_attr_accessor :bar }.new('foo')
133
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
134
+ foo.must_respond_to :bar=
135
+ foo.must_respond_to :bar
136
+ foo.bar = 'baz'
137
+ foo.bar.must_equal 'baz'
138
+ end
139
+
140
+ it 'provides a content accessor with conversion' do
141
+ foo = Class.new(Blather::XMPPNode) { content_attr_accessor :bar, :to_sym }.new('foo')
142
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
143
+ foo.must_respond_to :bar=
144
+ foo.must_respond_to :bar
145
+ foo.bar = 'baz'
146
+ foo.bar.must_equal :baz
147
+ end
148
+
149
+ it 'provides a content writer that removes a child when set to nil' do
150
+ foo = Class.new(Blather::XMPPNode) { content_attr_writer :bar }.new('foo')
151
+ foo << (bar = Blather::XMPPNode.new('bar', foo.document))
152
+ bar.content = 'baz'
153
+ foo.content_from(:bar).must_equal 'baz'
154
+ foo.xpath('bar').wont_be_empty
155
+
156
+ foo.must_respond_to :bar=
157
+ foo.bar = nil
158
+ foo.content_from(:bar).must_be_nil
159
+ foo.xpath('bar').must_be_empty
82
160
  end
83
161
 
84
162
  it 'can convert itself into a stanza' do
85
- class Foo < XMPPNode; register 'foo'; end
86
- n = XMPPNode.new('foo')
87
- n.to_stanza.must_be_kind_of Foo
163
+ class StanzaConvert < Blather::XMPPNode; register 'foo'; end
164
+ n = Blather::XMPPNode.new('foo')
165
+ n.to_stanza.must_be_kind_of StanzaConvert
88
166
  end
89
167
 
90
168
  it 'provides "attr_accessor" for namespace' do
91
- n = XMPPNode.new('foo')
169
+ n = Blather::XMPPNode.new('foo')
92
170
  n.namespace.must_be_nil
93
171
 
94
172
  n.namespace = 'foo:bar'
95
- n.namespace.must_equal 'foo:bar'
173
+ n.namespace_href.must_equal 'foo:bar'
96
174
  end
97
175
 
98
176
  it 'will remove a child element' do
99
- n = XMPPNode.new 'foo'
100
- n << XMPPNode.new('bar')
101
- n << XMPPNode.new('bar')
177
+ n = Blather::XMPPNode.new 'foo'
178
+ n << Blather::XMPPNode.new('bar', n.document)
179
+ n << Blather::XMPPNode.new('bar', n.document)
102
180
 
103
181
  n.find(:bar).size.must_equal 2
104
182
  n.remove_child 'bar'
@@ -106,9 +184,9 @@ describe 'Blather::XMPPNode' do
106
184
  end
107
185
 
108
186
  it 'will remove a child with a specific xmlns' do
109
- n = XMPPNode.new 'foo'
110
- n << XMPPNode.new('bar')
111
- c = XMPPNode.new('bar')
187
+ n = Blather::XMPPNode.new 'foo'
188
+ n << Blather::XMPPNode.new('bar')
189
+ c = Blather::XMPPNode.new('bar')
112
190
  c.namespace = 'foo:bar'
113
191
  n << c
114
192
 
@@ -119,31 +197,26 @@ describe 'Blather::XMPPNode' do
119
197
  end
120
198
 
121
199
  it 'will remove all child elements' do
122
- n = XMPPNode.new 'foo'
123
- n << XMPPNode.new('bar')
124
- n << XMPPNode.new('bar')
200
+ n = Blather::XMPPNode.new 'foo'
201
+ n << Blather::XMPPNode.new('bar')
202
+ n << Blather::XMPPNode.new('bar')
125
203
 
126
204
  n.find(:bar).size.must_equal 2
127
205
  n.remove_children 'bar'
128
206
  n.find(:bar).size.must_equal 0
129
207
  end
130
208
 
131
- it 'provides a helper to grab content from a child' do
132
- n = XMPPNode.new 'foo'
133
- n << XMPPNode.new('bar', 'baz')
134
- n.content_from(:bar).must_equal 'baz'
135
- end
136
-
137
209
  it 'provides a copy mechanism' do
138
- n = XMPPNode.new 'foo'
210
+ n = Blather::XMPPNode.new 'foo'
139
211
  n2 = n.copy
140
212
  n2.object_id.wont_equal n.object_id
141
213
  n2.element_name.must_equal n.element_name
142
214
  end
143
215
 
144
216
  it 'provides an inhert mechanism' do
145
- n = XMPPNode.new 'foo'
146
- n2 = XMPPNode.new 'foo', 'bar'
217
+ n = Blather::XMPPNode.new 'foo'
218
+ n2 = Blather::XMPPNode.new 'foo'
219
+ n2.content = 'bar'
147
220
  n2['foo'] = 'bar'
148
221
 
149
222
  n.inherit(n2)
@@ -152,32 +225,55 @@ describe 'Blather::XMPPNode' do
152
225
  end
153
226
 
154
227
  it 'provides a mechanism to inherit attrs' do
155
- n = XMPPNode.new 'foo'
156
- n2 = XMPPNode.new 'foo'
228
+ n = Blather::XMPPNode.new 'foo'
229
+ n2 = Blather::XMPPNode.new 'foo'
157
230
  n2['foo'] = 'bar'
158
231
 
159
232
  n.inherit_attrs(n2.attributes)
160
233
  n['foo'].must_equal 'bar'
161
234
  end
162
235
 
163
- it 'cuts line breaks out of #to_xml' do
164
- n = XMPPNode.new 'foo'
165
- n << XMPPNode.new('bar', 'baz')
166
- n.to_xml.scan(">\n<").size.must_equal 0
236
+ it 'has a content_from helper that pulls the content from a child node' do
237
+ f = Blather::XMPPNode.new('foo')
238
+ f << (b = Blather::XMPPNode.new('bar'))
239
+ b.content = 'content'
240
+ f.content_from(:bar).must_equal 'content'
167
241
  end
168
242
 
169
- it 'overrides #find to find without xpath' do
170
- n = XMPPNode.new 'foo'
171
- n << XMPPNode.new('bar', 'baz')
172
- n.find(:bar).must_be_kind_of Array
243
+ it 'returns nil when sent #content_from and a missing node' do
244
+ f = Blather::XMPPNode.new('foo')
245
+ f.content_from(:bar).must_be_nil
246
+ end
247
+
248
+ it 'creates a new node and sets content when sent #set_content_for' do
249
+ f = Blather::XMPPNode.new('foo')
250
+ f.must_respond_to :set_content_for
251
+ f.xpath('bar').must_be_empty
252
+ f.set_content_for :bar, :baz
253
+ f.xpath('bar').wont_be_empty
254
+ f.xpath('bar').first.content.must_equal 'baz'
255
+ end
173
256
 
174
- XML::Document.new.root = n
175
- n.find(:bar).must_be_kind_of XML::XPath::Object
257
+ it 'removes a child node when sent #set_content_for with nil' do
258
+ f = Blather::XMPPNode.new('foo')
259
+ f << (b = Blather::XMPPNode.new('bar'))
260
+ f.must_respond_to :set_content_for
261
+ f.xpath('bar').wont_be_empty
262
+ f.set_content_for :bar, nil
263
+ f.xpath('bar').must_be_empty
176
264
  end
177
265
 
178
- it 'can find using a symbol' do
179
- n = XMPPNode.new 'foo'
180
- n << XMPPNode.new('bar', 'baz')
181
- n.find(:bar).first.to_s.must_equal "<bar>baz</bar>"
266
+ it 'will change the content of an existing node when sent #set_content_for' do
267
+ f = Blather::XMPPNode.new('foo')
268
+ f << (b = Blather::XMPPNode.new('bar'))
269
+ b.content = 'baz'
270
+ f.must_respond_to :set_content_for
271
+ f.xpath('bar').wont_be_empty
272
+ f.xpath('bar').first.content.must_equal 'baz'
273
+ control = f.xpath('bar').first.pointer_id
274
+
275
+ f.set_content_for :bar, 'fiz'
276
+ f.xpath('bar').first.content.must_equal 'fiz'
277
+ f.xpath('bar').first.pointer_id.must_equal control
182
278
  end
183
279
  end