protocol-caldav 1.0.2 → 1.1.0
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.
- checksums.yaml +4 -4
- data/lib/protocol/caldav/collection.rb +99 -75
- data/lib/protocol/caldav/constants.rb +23 -27
- data/lib/protocol/caldav/content_line.rb +94 -91
- data/lib/protocol/caldav/ctag.rb +46 -50
- data/lib/protocol/caldav/etag.rb +24 -28
- data/lib/protocol/caldav/filter/addressbook.rb +41 -28
- data/lib/protocol/caldav/filter/calendar.rb +65 -44
- data/lib/protocol/caldav/filter/match.rb +632 -556
- data/lib/protocol/caldav/filter/parser.rb +273 -252
- data/lib/protocol/caldav/ical/component.rb +49 -46
- data/lib/protocol/caldav/ical/expand.rb +148 -134
- data/lib/protocol/caldav/ical/freebusy.rb +106 -74
- data/lib/protocol/caldav/ical/parser.rb +139 -139
- data/lib/protocol/caldav/ical/property.rb +22 -21
- data/lib/protocol/caldav/ical/rrule.rb +346 -235
- data/lib/protocol/caldav/item.rb +41 -43
- data/lib/protocol/caldav/multistatus.rb +43 -46
- data/lib/protocol/caldav/path.rb +82 -79
- data/lib/protocol/caldav/storage.rb +24 -28
- data/lib/protocol/caldav/vcard/card.rb +25 -27
- data/lib/protocol/caldav/vcard/parser.rb +56 -59
- data/lib/protocol/caldav/version.rb +1 -1
- data/lib/protocol/caldav/xml.rb +84 -80
- data/lib/protocol/caldav/xml_builder.rb +4 -2
- metadata +18 -4
data/lib/protocol/caldav/item.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
3
|
require "builder"
|
|
6
4
|
require "protocol/caldav"
|
|
7
5
|
|
|
@@ -74,52 +72,52 @@ module Protocol
|
|
|
74
72
|
end
|
|
75
73
|
end
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
def normalize(xml)
|
|
79
|
-
xml.gsub(/>\s+</, '><').strip
|
|
80
|
-
end
|
|
75
|
+
__END__
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
path: Protocol::Caldav::Path.new("/calendars/admin/work/event.ics"),
|
|
86
|
-
body: "BEGIN:VCALENDAR\r\nEND:VCALENDAR",
|
|
87
|
-
content_type: "text/calendar",
|
|
88
|
-
etag: '"abc123"'
|
|
89
|
-
}
|
|
90
|
-
Protocol::Caldav::Item.new(**defaults.merge(opts))
|
|
91
|
-
end
|
|
77
|
+
def normalize(xml)
|
|
78
|
+
xml.gsub(/>\s+</, '><').strip
|
|
79
|
+
end
|
|
92
80
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
describe "Protocol::Caldav::Item" do
|
|
82
|
+
def make_item(**opts)
|
|
83
|
+
defaults = {
|
|
84
|
+
path: Protocol::Caldav::Path.new("/calendars/admin/work/event.ics"),
|
|
85
|
+
body: "BEGIN:VCALENDAR\r\nEND:VCALENDAR",
|
|
86
|
+
content_type: "text/calendar",
|
|
87
|
+
etag: '"abc123"'
|
|
88
|
+
}
|
|
89
|
+
Protocol::Caldav::Item.new(**defaults.merge(opts))
|
|
90
|
+
end
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
it "exposes path, body, content_type, etag" do
|
|
93
|
+
item = make_item
|
|
94
|
+
item.path.to_s.should.equal "/calendars/admin/work/event.ics"
|
|
95
|
+
item.body.should.include "VCALENDAR"
|
|
96
|
+
item.content_type.should.equal "text/calendar"
|
|
97
|
+
item.etag.should.equal '"abc123"'
|
|
98
|
+
end
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
xml.should.include "text/calendar"
|
|
111
|
-
end
|
|
100
|
+
it "new? returns new_record state" do
|
|
101
|
+
make_item(new_record: true).new?.should.equal true
|
|
102
|
+
make_item(new_record: false).new?.should.equal false
|
|
103
|
+
end
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
it "to_propfind_xml includes etag and content-type" do
|
|
106
|
+
xml = make_item.to_propfind_xml
|
|
107
|
+
xml.should.include "getetag"
|
|
108
|
+
xml.should.include "getcontenttype"
|
|
109
|
+
xml.should.include "text/calendar"
|
|
110
|
+
end
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
it "to_report_xml includes data tag with body" do
|
|
113
|
+
xml = make_item.to_report_xml(data_tag: "c:calendar-data")
|
|
114
|
+
xml.should.include "c:calendar-data"
|
|
115
|
+
xml.should.include "VCALENDAR"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "to_propname_xml includes empty prop elements" do
|
|
119
|
+
xml = make_item.to_propname_xml
|
|
120
|
+
xml.should.include "<d:getetag/>"
|
|
121
|
+
xml.should.include "<d:getcontenttype/>"
|
|
124
122
|
end
|
|
125
123
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
3
|
require "builder"
|
|
6
4
|
|
|
7
5
|
module Protocol
|
|
@@ -24,60 +22,59 @@ module Protocol
|
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
|
|
25
|
+
__END__
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
27
|
+
def normalize(xml)
|
|
28
|
+
xml.gsub(/>\s+</, '><').strip
|
|
29
|
+
end
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
# Minimal wrapper so tests can pass objects with build_xml
|
|
32
|
+
class FakeResponse
|
|
33
|
+
def initialize(href)
|
|
34
|
+
@href = href
|
|
35
|
+
end
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
end
|
|
37
|
+
def build_xml(xml)
|
|
38
|
+
xml.tag!("d:response") { xml.tag!("d:href", @href) }
|
|
42
39
|
end
|
|
40
|
+
end
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
describe "Protocol::Caldav::Multistatus" do
|
|
43
|
+
it "declares all four required namespaces" do
|
|
44
|
+
xml = Protocol::Caldav::Multistatus.new([]).to_xml
|
|
45
|
+
xml.should.include 'xmlns:d="DAV:"'
|
|
46
|
+
xml.should.include 'xmlns:c="urn:ietf:params:xml:ns:caldav"'
|
|
47
|
+
xml.should.include 'xmlns:cr="urn:ietf:params:xml:ns:carddav"'
|
|
48
|
+
xml.should.include 'xmlns:cs="http://calendarserver.org/ns/"'
|
|
49
|
+
end
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
it "emits responses in the order given" do
|
|
52
|
+
responses = [FakeResponse.new("/a"), FakeResponse.new("/b")]
|
|
53
|
+
xml = Protocol::Caldav::Multistatus.new(responses).to_xml
|
|
54
|
+
xml.index("/a").should.be < xml.index("/b")
|
|
55
|
+
end
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
it "produces valid XML for empty response array" do
|
|
58
|
+
xml = Protocol::Caldav::Multistatus.new([]).to_xml
|
|
59
|
+
xml.should.include '<?xml version="1.0"'
|
|
60
|
+
xml.should.include '<d:multistatus'
|
|
61
|
+
xml.should.include '</d:multistatus>'
|
|
62
|
+
end
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
end
|
|
70
|
-
xml.should.include '&'
|
|
71
|
-
xml.should.not.include '&amp;'
|
|
64
|
+
it "does not double-escape when using builder" do
|
|
65
|
+
xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
|
|
66
|
+
x.tag!("d:response") { x.tag!("d:href", "/Work & Personal") }
|
|
72
67
|
end
|
|
68
|
+
xml.should.include '&'
|
|
69
|
+
xml.should.not.include '&amp;'
|
|
70
|
+
end
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
end
|
|
79
|
-
xml.should.include '/test'
|
|
80
|
-
xml.should.include 'token-123'
|
|
72
|
+
it "supports block form for custom content" do
|
|
73
|
+
xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
|
|
74
|
+
x.tag!("d:response") { x.tag!("d:href", "/test") }
|
|
75
|
+
x.tag!("d:sync-token", "token-123")
|
|
81
76
|
end
|
|
77
|
+
xml.should.include '/test'
|
|
78
|
+
xml.should.include 'token-123'
|
|
82
79
|
end
|
|
83
80
|
end
|
data/lib/protocol/caldav/path.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
3
|
require "builder"
|
|
6
4
|
require "protocol/caldav"
|
|
7
5
|
|
|
@@ -12,7 +10,9 @@ module Protocol
|
|
|
12
10
|
|
|
13
11
|
def initialize(raw, storage_class: nil)
|
|
14
12
|
p = raw.to_s.gsub(%r{/+}, '/')
|
|
15
|
-
|
|
13
|
+
unless p.start_with?('/')
|
|
14
|
+
p = "/#{p}"
|
|
15
|
+
end
|
|
16
16
|
@to_s = p
|
|
17
17
|
@storage_class = storage_class
|
|
18
18
|
end
|
|
@@ -32,7 +32,9 @@ module Protocol
|
|
|
32
32
|
|
|
33
33
|
def child_of?(other)
|
|
34
34
|
parent_str = other.to_s
|
|
35
|
-
|
|
35
|
+
unless parent_str.end_with?('/')
|
|
36
|
+
parent_str = "#{parent_str}/"
|
|
37
|
+
end
|
|
36
38
|
if @to_s.start_with?(parent_str)
|
|
37
39
|
remainder = @to_s[parent_str.length..]
|
|
38
40
|
remainder.chomp('/').count('/').zero? && !remainder.chomp('/').empty?
|
|
@@ -42,7 +44,9 @@ module Protocol
|
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def parent_exists?
|
|
45
|
-
|
|
47
|
+
unless @storage_class
|
|
48
|
+
raise ArgumentError, "storage_class required for parent_exists?"
|
|
49
|
+
end
|
|
46
50
|
|
|
47
51
|
if parent.depth <= 2
|
|
48
52
|
true
|
|
@@ -88,103 +92,102 @@ module Protocol
|
|
|
88
92
|
end
|
|
89
93
|
end
|
|
90
94
|
|
|
95
|
+
__END__
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
end
|
|
97
|
+
describe "Protocol::Caldav::Path" do
|
|
98
|
+
it "normalizes // to /" do
|
|
99
|
+
Protocol::Caldav::Path.new("//foo//bar").to_s.should.equal "/foo/bar"
|
|
100
|
+
end
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
it "normalizes leading ///foo to /foo" do
|
|
103
|
+
Protocol::Caldav::Path.new("///foo").to_s.should.equal "/foo"
|
|
104
|
+
end
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
it "adds leading / if missing" do
|
|
107
|
+
Protocol::Caldav::Path.new("foo/bar").to_s.should.equal "/foo/bar"
|
|
108
|
+
end
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
describe "#parent" do
|
|
111
|
+
it "parent of /a/b/c/ is /a/b/" do
|
|
112
|
+
Protocol::Caldav::Path.new("/a/b/c/").parent.to_s.should.equal "/a/b/"
|
|
113
|
+
end
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
it "parent of /a/ is /" do
|
|
116
|
+
Protocol::Caldav::Path.new("/a/").parent.to_s.should.equal "/"
|
|
117
|
+
end
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
end
|
|
119
|
+
it "parent of / is / (idempotent)" do
|
|
120
|
+
Protocol::Caldav::Path.new("/").parent.to_s.should.equal "/"
|
|
118
121
|
end
|
|
122
|
+
end
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
describe "#depth" do
|
|
125
|
+
it "depth of / is 0" do
|
|
126
|
+
Protocol::Caldav::Path.new("/").depth.should.equal 0
|
|
127
|
+
end
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
it "depth of /a/ is 1" do
|
|
130
|
+
Protocol::Caldav::Path.new("/a/").depth.should.equal 1
|
|
131
|
+
end
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
end
|
|
133
|
+
it "depth of /a/b is 2 (trailing-slash-insensitive)" do
|
|
134
|
+
Protocol::Caldav::Path.new("/a/b").depth.should.equal 2
|
|
132
135
|
end
|
|
136
|
+
end
|
|
133
137
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
describe "#child_of?" do
|
|
139
|
+
it "returns true for direct child" do
|
|
140
|
+
child = Protocol::Caldav::Path.new("/a/b/")
|
|
141
|
+
parent = Protocol::Caldav::Path.new("/a/")
|
|
142
|
+
child.child_of?(parent).should.equal true
|
|
143
|
+
end
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
it "returns false for grandchild" do
|
|
146
|
+
grandchild = Protocol::Caldav::Path.new("/a/b/c/")
|
|
147
|
+
grandparent = Protocol::Caldav::Path.new("/a/")
|
|
148
|
+
grandchild.child_of?(grandparent).should.equal false
|
|
149
|
+
end
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
it "returns false for sibling" do
|
|
152
|
+
a = Protocol::Caldav::Path.new("/a/b/")
|
|
153
|
+
b = Protocol::Caldav::Path.new("/a/c/")
|
|
154
|
+
a.child_of?(b).should.equal false
|
|
155
|
+
end
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
end
|
|
157
|
+
it "returns false for self" do
|
|
158
|
+
a = Protocol::Caldav::Path.new("/a/")
|
|
159
|
+
a.child_of?(a).should.equal false
|
|
157
160
|
end
|
|
161
|
+
end
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
describe "#ensure_trailing_slash" do
|
|
164
|
+
it "is idempotent on a slashed path" do
|
|
165
|
+
Protocol::Caldav::Path.new("/a/").ensure_trailing_slash.to_s.should.equal "/a/"
|
|
166
|
+
end
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
end
|
|
168
|
+
it "adds slash to unslashed" do
|
|
169
|
+
Protocol::Caldav::Path.new("/a").ensure_trailing_slash.to_s.should.equal "/a/"
|
|
167
170
|
end
|
|
171
|
+
end
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
end
|
|
173
|
+
describe "#start_with?" do
|
|
174
|
+
it "delegates to string semantics" do
|
|
175
|
+
Protocol::Caldav::Path.new("/calendars/admin/").start_with?("/calendars/").should.equal true
|
|
176
|
+
Protocol::Caldav::Path.new("/addressbooks/admin/").start_with?("/calendars/").should.equal false
|
|
174
177
|
end
|
|
178
|
+
end
|
|
175
179
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
describe "#==" do
|
|
181
|
+
it "paths with same string are equal" do
|
|
182
|
+
a = Protocol::Caldav::Path.new("/a/")
|
|
183
|
+
b = Protocol::Caldav::Path.new("/a/")
|
|
184
|
+
(a == b).should.equal true
|
|
185
|
+
end
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
end
|
|
187
|
+
it "paths from different storage_class but same string compare equal" do
|
|
188
|
+
a = Protocol::Caldav::Path.new("/a/", storage_class: Object.new)
|
|
189
|
+
b = Protocol::Caldav::Path.new("/a/", storage_class: Object.new)
|
|
190
|
+
(a == b).should.equal true
|
|
188
191
|
end
|
|
189
192
|
end
|
|
190
193
|
end
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
|
|
6
3
|
module Protocol
|
|
7
4
|
module Caldav
|
|
8
5
|
class Storage
|
|
@@ -86,35 +83,34 @@ module Protocol
|
|
|
86
83
|
end
|
|
87
84
|
end
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
86
|
+
__END__
|
|
87
|
+
|
|
88
|
+
describe "Protocol::Caldav::Storage" do
|
|
89
|
+
it "every method raises NotImplementedError" do
|
|
90
|
+
s = Protocol::Caldav::Storage.new
|
|
91
|
+
methods = %i[create_collection get_collection delete_collection list_collections
|
|
92
|
+
update_collection collection_exists? get_item put_item delete_item
|
|
93
|
+
list_items move_item get_multi exists? etag snapshot_sync sync_changes]
|
|
94
|
+
three_arg = %i[put_item]
|
|
95
|
+
two_arg = %i[update_collection move_item sync_changes]
|
|
96
|
+
methods.each do |m|
|
|
97
|
+
if three_arg.include?(m)
|
|
98
|
+
lambda { s.send(m, "/x", "body", "ct") }.should.raise NotImplementedError
|
|
99
|
+
elsif two_arg.include?(m)
|
|
100
|
+
lambda { s.send(m, "/x", {}) }.should.raise NotImplementedError
|
|
101
|
+
else
|
|
102
|
+
lambda { s.send(m, "/x") }.should.raise NotImplementedError
|
|
107
103
|
end
|
|
108
104
|
end
|
|
105
|
+
end
|
|
109
106
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
end
|
|
107
|
+
it "can be subclassed with partial implementation" do
|
|
108
|
+
klass = Class.new(Protocol::Caldav::Storage) do
|
|
109
|
+
def exists?(path)
|
|
110
|
+
true
|
|
115
111
|
end
|
|
116
|
-
klass.new.exists?("/x").should.equal true
|
|
117
|
-
lambda { klass.new.get_item("/x") }.should.raise NotImplementedError
|
|
118
112
|
end
|
|
113
|
+
klass.new.exists?("/x").should.equal true
|
|
114
|
+
lambda { klass.new.get_item("/x") }.should.raise NotImplementedError
|
|
119
115
|
end
|
|
120
116
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
3
|
require "protocol/caldav"
|
|
6
4
|
|
|
7
5
|
module Protocol
|
|
@@ -24,35 +22,35 @@ module Protocol
|
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
describe "Protocol::Caldav::Vcard::Card" do
|
|
29
|
-
def prop(name, value)
|
|
30
|
-
Protocol::Caldav::Ical::Property.new(name: name, params: {}, value: value)
|
|
31
|
-
end
|
|
25
|
+
__END__
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
describe "Protocol::Caldav::Vcard::Card" do
|
|
28
|
+
def prop(name, value)
|
|
29
|
+
Protocol::Caldav::Ical::Property.new(name: name, params: {}, value: value)
|
|
30
|
+
end
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
it "find_property returns the first matching property" do
|
|
33
|
+
card = Protocol::Caldav::Vcard::Card.new(properties: [prop("FN", "John"), prop("FN", "Jane")])
|
|
34
|
+
card.find_property("FN").value.should.equal "John"
|
|
35
|
+
end
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
it "find_property is case-insensitive" do
|
|
38
|
+
card = Protocol::Caldav::Vcard::Card.new(properties: [prop("FN", "John")])
|
|
39
|
+
card.find_property("fn").value.should.equal "John"
|
|
40
|
+
end
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
it "find_property returns nil when absent" do
|
|
43
|
+
card = Protocol::Caldav::Vcard::Card.new(properties: [])
|
|
44
|
+
card.find_property("FN").should.be.nil
|
|
45
|
+
end
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
it "has no sub-component finder (vCards don't nest)" do
|
|
48
|
+
card = Protocol::Caldav::Vcard::Card.new
|
|
49
|
+
card.should.not.respond_to(:find_components)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "find_all_properties returns all matching" do
|
|
53
|
+
card = Protocol::Caldav::Vcard::Card.new(properties: [prop("TEL", "123"), prop("EMAIL", "x"), prop("TEL", "456")])
|
|
54
|
+
card.find_all_properties("TEL").length.should.equal 2
|
|
57
55
|
end
|
|
58
56
|
end
|