twitter-text-kow 1.3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,136 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: utf-8
6
+ require File.dirname(__FILE__) + '/spec_helper'
7
+
8
+ describe Twitter::TwitterText::Configuration do
9
+ context "configuration" do
10
+ context "with invalid data" do
11
+ it "should raise an exception" do
12
+ invalid_hash = Twitter::TwitterText::Configuration.parse_string("{\"version\":2,\"maxWeightedTweetLength\":280,\"scale\":100,\"defaultWeight\":200,\"transformedURLLength\":23,\"ranges\":[{\"start\":0,\"end\":true,\"weight\":false},{\"start\":8192,\"end\":8205,\"weight\":100},{\"start\":8208,\"end\":8223,\"weight\":100},{\"start\":8242,\"end\":8247,\"weight\":100}]}")
13
+ expect { Twitter::TwitterText::Configuration.new(invalid_hash) }.to raise_error(ArgumentError)
14
+ end
15
+ end
16
+
17
+ context "with defaults" do
18
+ before do
19
+ Twitter::TwitterText::Configuration.default_configuration = Twitter::TwitterText::Configuration.configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V2)
20
+ end
21
+
22
+ it "should define version constants" do
23
+ expect(Twitter::TwitterText::Configuration.const_defined?(:CONFIG_V1)).to be true
24
+ expect(Twitter::TwitterText::Configuration.const_defined?(:CONFIG_V2)).to be true
25
+ expect(Twitter::TwitterText::Configuration.const_defined?(:CONFIG_V3)).to be true
26
+ end
27
+
28
+ it "should define a default configuration" do
29
+ expect(Twitter::TwitterText::Configuration.default_configuration).to_not be_nil
30
+ expect(Twitter::TwitterText::Configuration.default_configuration.version).to eq(2)
31
+ end
32
+ end
33
+
34
+ context "with v1 configuration" do
35
+ before do
36
+ @config = Twitter::TwitterText::Configuration.configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V1)
37
+ end
38
+
39
+ it "should have a version" do
40
+ expect(@config.version).to eq(1)
41
+ end
42
+
43
+ it "should have a max_weighted_tweet_length" do
44
+ expect(@config.max_weighted_tweet_length).to eq(140)
45
+ end
46
+
47
+ it "should have a scale" do
48
+ expect(@config.scale).to eq(1)
49
+ end
50
+
51
+ it "should have a default_weight" do
52
+ expect(@config.default_weight).to eq(1)
53
+ end
54
+
55
+ it "should have a transformed_url_length" do
56
+ expect(@config.transformed_url_length).to eq(23)
57
+ end
58
+ end
59
+
60
+ context "with v2 configuration" do
61
+ before do
62
+ @config = Twitter::TwitterText::Configuration.configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V2)
63
+ end
64
+
65
+ it "should have a version" do
66
+ expect(@config.version).to eq(2)
67
+ end
68
+
69
+ it "should have a max_weighted_tweet_length" do
70
+ expect(@config.max_weighted_tweet_length).to eq(280)
71
+ end
72
+
73
+ it "should have a scale" do
74
+ expect(@config.scale).to eq(100)
75
+ end
76
+
77
+ it "should have a default_weight" do
78
+ expect(@config.default_weight).to eq(200)
79
+ end
80
+
81
+ it "should have a transformed_url_length" do
82
+ expect(@config.transformed_url_length).to eq(23)
83
+ end
84
+
85
+ it "should have a configured range" do
86
+ expect(@config.ranges).to be_kind_of(Array)
87
+ expect(@config.ranges.count).to be > 0
88
+ expect(@config.ranges[0]).to be_kind_of(Twitter::TwitterText::WeightedRange)
89
+ weighted_range = @config.ranges[0]
90
+ expect(weighted_range.start).to be_kind_of(Integer)
91
+ expect(weighted_range.end).to be_kind_of(Integer)
92
+ expect(weighted_range.weight).to be_kind_of(Integer)
93
+ end
94
+ end
95
+
96
+ context "with v3 configuration" do
97
+ before do
98
+ @config = Twitter::TwitterText::Configuration.configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V3)
99
+ end
100
+
101
+ it "should have a version" do
102
+ expect(@config.version).to eq(3)
103
+ end
104
+
105
+ it "should have a max_weighted_tweet_length" do
106
+ expect(@config.max_weighted_tweet_length).to eq(280)
107
+ end
108
+
109
+ it "should have a scale" do
110
+ expect(@config.scale).to eq(100)
111
+ end
112
+
113
+ it "should have a default_weight" do
114
+ expect(@config.default_weight).to eq(200)
115
+ end
116
+
117
+ it "should have a transformed_url_length" do
118
+ expect(@config.transformed_url_length).to eq(23)
119
+ end
120
+
121
+ it "should have a configured range" do
122
+ expect(@config.ranges).to be_kind_of(Array)
123
+ expect(@config.ranges.count).to be > 0
124
+ expect(@config.ranges[0]).to be_kind_of(Twitter::TwitterText::WeightedRange)
125
+ weighted_range = @config.ranges[0]
126
+ expect(weighted_range.start).to be_kind_of(Integer)
127
+ expect(weighted_range.end).to be_kind_of(Integer)
128
+ expect(weighted_range.weight).to be_kind_of(Integer)
129
+ end
130
+
131
+ it "should support discounting emoji" do
132
+ expect(@config.emoji_parsing_enabled).to be true
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,392 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: utf-8
6
+ require File.dirname(__FILE__) + '/spec_helper'
7
+
8
+ class TestExtractor
9
+ include Twitter::TwitterText::Extractor
10
+ end
11
+
12
+ describe Twitter::TwitterText::Extractor do
13
+ before do
14
+ @extractor = TestExtractor.new
15
+ end
16
+
17
+ describe "mentions" do
18
+ context "single screen name alone " do
19
+ it "should be linked" do
20
+ expect(@extractor.extract_mentioned_screen_names("@alice")).to be == ["alice"]
21
+ end
22
+
23
+ it "should be linked with _" do
24
+ expect(@extractor.extract_mentioned_screen_names("@alice_adams")).to be == ["alice_adams"]
25
+ end
26
+
27
+ it "should be linked if numeric" do
28
+ expect(@extractor.extract_mentioned_screen_names("@1234")).to be == ["1234"]
29
+ end
30
+ end
31
+
32
+ context "multiple screen names" do
33
+ it "should both be linked" do
34
+ expect(@extractor.extract_mentioned_screen_names("@alice @bob")).to be == ["alice", "bob"]
35
+ end
36
+ end
37
+
38
+ context "screen names embedded in text" do
39
+ it "should be linked in Latin text" do
40
+ expect(@extractor.extract_mentioned_screen_names("waiting for @alice to arrive")).to be == ["alice"]
41
+ end
42
+
43
+ it "should be linked in Japanese text" do
44
+ expect(@extractor.extract_mentioned_screen_names("の@aliceに到着を待っている")).to be == ["alice"]
45
+ end
46
+
47
+ it "should ignore mentions preceded by !, @, #, $, %, & or *" do
48
+ invalid_chars = ['!', '@', '#', '$', '%', '&', '*']
49
+ invalid_chars.each do |c|
50
+ expect(@extractor.extract_mentioned_screen_names("f#{c}@kn")).to be == []
51
+ end
52
+ end
53
+ end
54
+
55
+ it "should accept a block arugment and call it in order" do
56
+ needed = ["alice", "bob"]
57
+ @extractor.extract_mentioned_screen_names("@alice @bob") do |sn|
58
+ expect(sn).to be == needed.shift
59
+ end
60
+ expect(needed).to be == []
61
+ end
62
+ end
63
+
64
+ describe "mentions with indices" do
65
+ context "single screen name alone " do
66
+ it "should be linked and the correct indices" do
67
+ expect(@extractor.extract_mentioned_screen_names_with_indices("@alice")).to be == [{:screen_name => "alice", :indices => [0, 6]}]
68
+ end
69
+
70
+ it "should be linked with _ and the correct indices" do
71
+ expect(@extractor.extract_mentioned_screen_names_with_indices("@alice_adams")).to be == [{:screen_name => "alice_adams", :indices => [0, 12]}]
72
+ end
73
+
74
+ it "should be linked if numeric and the correct indices" do
75
+ expect(@extractor.extract_mentioned_screen_names_with_indices("@1234")).to be == [{:screen_name => "1234", :indices => [0, 5]}]
76
+ end
77
+ end
78
+
79
+ context "multiple screen names" do
80
+ it "should both be linked with the correct indices" do
81
+ expect(@extractor.extract_mentioned_screen_names_with_indices("@alice @bob")).to be ==
82
+ [{:screen_name => "alice", :indices => [0, 6]},
83
+ {:screen_name => "bob", :indices => [7, 11]}]
84
+ end
85
+
86
+ it "should be linked with the correct indices even when repeated" do
87
+ expect(@extractor.extract_mentioned_screen_names_with_indices("@alice @alice @bob")).to be ==
88
+ [{:screen_name => "alice", :indices => [0, 6]},
89
+ {:screen_name => "alice", :indices => [7, 13]},
90
+ {:screen_name => "bob", :indices => [14, 18]}]
91
+ end
92
+ end
93
+
94
+ context "screen names embedded in text" do
95
+ it "should be linked in Latin text with the correct indices" do
96
+ expect(@extractor.extract_mentioned_screen_names_with_indices("waiting for @alice to arrive")).to be == [{:screen_name => "alice", :indices => [12, 18]}]
97
+ end
98
+
99
+ it "should be linked in Japanese text with the correct indices" do
100
+ expect(@extractor.extract_mentioned_screen_names_with_indices("の@aliceに到着を待っている")).to be == [{:screen_name => "alice", :indices => [1, 7]}]
101
+ end
102
+ end
103
+
104
+ it "should accept a block arugment and call it in order" do
105
+ needed = [{:screen_name => "alice", :indices => [0, 6]}, {:screen_name => "bob", :indices => [7, 11]}]
106
+ @extractor.extract_mentioned_screen_names_with_indices("@alice @bob") do |sn, start_index, end_index|
107
+ data = needed.shift
108
+ expect(sn).to be == data[:screen_name]
109
+ expect(start_index).to be == data[:indices].first
110
+ expect(end_index).to be == data[:indices].last
111
+ end
112
+ expect(needed).to be == []
113
+ end
114
+
115
+ it "should extract screen name in text with supplementary character" do
116
+ expect(@extractor.extract_mentioned_screen_names_with_indices("#{[0x10400].pack('U')} @alice")).to be == [{:screen_name => "alice", :indices => [2, 8]}]
117
+ end
118
+ end
119
+
120
+ describe "replies" do
121
+ context "should be extracted from" do
122
+ it "should extract from lone name" do
123
+ expect(@extractor.extract_reply_screen_name("@alice")).to be == "alice"
124
+ end
125
+
126
+ it "should extract from the start" do
127
+ expect(@extractor.extract_reply_screen_name("@alice reply text")).to be == "alice"
128
+ end
129
+
130
+ it "should extract preceded by a space" do
131
+ expect(@extractor.extract_reply_screen_name(" @alice reply text")).to be == "alice"
132
+ end
133
+
134
+ it "should extract preceded by a full-width space" do
135
+ expect(@extractor.extract_reply_screen_name("#{[0x3000].pack('U')}@alice reply text")).to be == "alice"
136
+ end
137
+ end
138
+
139
+ context "should not be extracted from" do
140
+ it "should not be extracted when preceded by text" do
141
+ expect(@extractor.extract_reply_screen_name("reply @alice text")).to be == nil
142
+ end
143
+
144
+ it "should not be extracted when preceded by puctuation" do
145
+ %w(. / _ - + # ! @).each do |punct|
146
+ expect(@extractor.extract_reply_screen_name("#{punct}@alice text")).to be == nil
147
+ end
148
+ end
149
+ end
150
+
151
+ context "should accept a block arugment" do
152
+ it "should call the block on match" do
153
+ @extractor.extract_reply_screen_name("@alice") do |sn|
154
+ expect(sn).to be == "alice"
155
+ end
156
+ end
157
+
158
+ it "should not call the block on no match" do
159
+ calls = 0
160
+ @extractor.extract_reply_screen_name("not a reply") do |sn|
161
+ calls += 1
162
+ end
163
+ expect(calls).to be == 0
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "urls" do
169
+ describe "matching URLS" do
170
+ TestUrls::VALID.each do |url|
171
+ it "should extract the URL #{url} and prefix it with a protocol if missing" do
172
+ expect(@extractor.extract_urls(url).first).to include(url)
173
+ end
174
+
175
+ it "should match the URL #{url} when it's embedded in other text" do
176
+ text = "Sweet url: #{url} I found. #awesome"
177
+ expect(@extractor.extract_urls(text).first).to include(url)
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "invalid URLS" do
183
+ TestUrls::INVALID.each do |url|
184
+ it "does not extract URL from #{url}" do
185
+ expect(@extractor.extract_urls(url).first).to be nil
186
+ end
187
+ end
188
+ end
189
+
190
+ describe "t.co URLS" do
191
+ TestUrls::TCO.each do |url|
192
+ it "should only extract the t.co URL from the URL #{url}" do
193
+ extracted_urls = @extractor.extract_urls(url)
194
+ expect(extracted_urls.size).to be == 1
195
+ extracted_url = extracted_urls.first
196
+ expect(extracted_url).to_not be == url
197
+ expect(extracted_url).to be == url[0...20]
198
+ end
199
+
200
+ it "should match the t.co URL from the URL #{url} when it's embedded in other text" do
201
+ text = "Sweet url: #{url} I found. #awesome"
202
+ extracted_urls = @extractor.extract_urls(text)
203
+ expect(extracted_urls.size).to be == 1
204
+ extracted_url = extracted_urls.first
205
+ expect(extracted_url).to_not be == url
206
+ expect(extracted_url).to be == url[0...20]
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "urls with indices" do
213
+ describe "matching URLS" do
214
+ TestUrls::VALID.each do |url|
215
+ it "should extract the URL #{url} and prefix it with a protocol if missing" do
216
+ extracted_urls = @extractor.extract_urls_with_indices(url)
217
+ expect(extracted_urls.size).to be == 1
218
+ extracted_url = extracted_urls.first
219
+ expect(extracted_url[:url]).to include(url)
220
+ expect(extracted_url[:indices].first).to be == 0
221
+ expect(extracted_url[:indices].last).to be == url.chars.to_a.size
222
+ end
223
+
224
+ it "should match the URL #{url} when it's embedded in other text" do
225
+ text = "Sweet url: #{url} I found. #awesome"
226
+ extracted_urls = @extractor.extract_urls_with_indices(text)
227
+ expect(extracted_urls.size).to be == 1
228
+ extracted_url = extracted_urls.first
229
+ expect(extracted_url[:url]).to include(url)
230
+ expect(extracted_url[:indices].first).to be == 11
231
+ expect(extracted_url[:indices].last).to be == 11 + url.chars.to_a.size
232
+ end
233
+ end
234
+
235
+ it "should extract URL in text with supplementary character" do
236
+ expect(@extractor.extract_urls_with_indices("#{[0x10400].pack('U')} http://twitter.com")).to be == [{:url => "http://twitter.com", :indices => [2, 20]}]
237
+ end
238
+ end
239
+
240
+ describe "invalid URLS" do
241
+ it "does not link urls with invalid domains" do
242
+ expect(@extractor.extract_urls_with_indices("http://tld-too-short.x")).to be == []
243
+ end
244
+
245
+ it "does not consider a long URL with protocol to be valid" do
246
+ # maximum length of domain label is 32 chars.
247
+ url = ("a" * 31) + "."
248
+ url *= (Twitter::TwitterText::Extractor::MAX_URL_LENGTH / 32)
249
+ url = "https://" + url + "com" # longer than 4096 (MAX_URL_LENGTH) chars
250
+ expect(@extractor.is_valid_domain(url.length, url, true)).to be false
251
+ end
252
+
253
+ it "does not consider a long URL without protocol to be valid" do
254
+ # maximum length of domain label is 32 chars.
255
+ url = ("a" * 31) + "."
256
+ url *= ((Twitter::TwitterText::Extractor::MAX_URL_LENGTH / 32) - 1)
257
+ url = url + "com" # shorter than 4096 (MAX_URL_LENGTH) chars
258
+ expect(@extractor.is_valid_domain(url.length, url, false)).to be true
259
+ url = ("a" * (31 - "https://".length)) + "." + url
260
+ expect(@extractor.is_valid_domain(url.length, url, false)).to be false
261
+ end
262
+ end
263
+
264
+ describe "t.co URLS" do
265
+ TestUrls::TCO.each do |url|
266
+ it "should only extract the t.co URL from the URL #{url} and adjust indices correctly" do
267
+ extracted_urls = @extractor.extract_urls_with_indices(url)
268
+ expect(extracted_urls.size).to be == 1
269
+ extracted_url = extracted_urls.first
270
+ expect(extracted_url[:url]).to_not include(url)
271
+ expect(extracted_url[:url]).to include(url[0...20])
272
+ expect(extracted_url[:indices].first).to be == 0
273
+ expect(extracted_url[:indices].last).to be == 20
274
+ end
275
+
276
+ it "should match the t.co URL from the URL #{url} when it's embedded in other text" do
277
+ text = "Sweet url: #{url} I found. #awesome"
278
+ extracted_urls = @extractor.extract_urls_with_indices(text)
279
+ expect(extracted_urls.size).to be == 1
280
+ extracted_url = extracted_urls.first
281
+ expect(extracted_url[:url]).to_not include(url)
282
+ expect(extracted_url[:url]).to include(url[0...20])
283
+ expect(extracted_url[:indices].first).to be == 11
284
+ expect(extracted_url[:indices].last).to be == 31
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ describe "hashtags" do
291
+ context "extracts latin/numeric hashtags" do
292
+ %w(text text123 123text).each do |hashtag|
293
+ it "should extract ##{hashtag}" do
294
+ expect(@extractor.extract_hashtags("##{hashtag}")).to be == [hashtag]
295
+ end
296
+
297
+ it "should extract ##{hashtag} within text" do
298
+ expect(@extractor.extract_hashtags("pre-text ##{hashtag} post-text")).to be == [hashtag]
299
+ end
300
+ end
301
+ end
302
+
303
+ context "international hashtags" do
304
+ context "should allow accents" do
305
+ %w(mañana café münchen).each do |hashtag|
306
+ it "should extract ##{hashtag}" do
307
+ expect(@extractor.extract_hashtags("##{hashtag}")).to be == [hashtag]
308
+ end
309
+
310
+ it "should extract ##{hashtag} within text" do
311
+ expect(@extractor.extract_hashtags("pre-text ##{hashtag} post-text")).to be == [hashtag]
312
+ end
313
+ end
314
+
315
+ it "should not allow the multiplication character" do
316
+ expect(@extractor.extract_hashtags("#pre#{Twitter::TwitterText::Unicode::U00D7}post")).to be == ["pre"]
317
+ end
318
+
319
+ it "should not allow the division character" do
320
+ expect(@extractor.extract_hashtags("#pre#{Twitter::TwitterText::Unicode::U00F7}post")).to be == ["pre"]
321
+ end
322
+ end
323
+
324
+ end
325
+
326
+ it "should not extract numeric hashtags" do
327
+ expect(@extractor.extract_hashtags("#1234")).to be == []
328
+ end
329
+
330
+ it "should extract hashtag followed by punctuations" do
331
+ expect(@extractor.extract_hashtags("#test1: #test2; #test3\"")).to be == ["test1", "test2" ,"test3"]
332
+ end
333
+ end
334
+
335
+ describe "hashtags with indices" do
336
+ def match_hashtag_in_text(hashtag, text, offset = 0)
337
+ extracted_hashtags = @extractor.extract_hashtags_with_indices(text)
338
+ expect(extracted_hashtags.size).to be == 1
339
+ extracted_hashtag = extracted_hashtags.first
340
+ expect(extracted_hashtag[:hashtag]).to be == hashtag
341
+ expect(extracted_hashtag[:indices].first).to be == offset
342
+ expect(extracted_hashtag[:indices].last).to be == offset + hashtag.chars.to_a.size + 1
343
+ end
344
+
345
+ def not_match_hashtag_in_text(text)
346
+ extracted_hashtags = @extractor.extract_hashtags_with_indices(text)
347
+ expect(extracted_hashtags.size).to be == 0
348
+ end
349
+
350
+ context "extracts latin/numeric hashtags" do
351
+ %w(text text123 123text).each do |hashtag|
352
+ it "should extract ##{hashtag}" do
353
+ match_hashtag_in_text(hashtag, "##{hashtag}")
354
+ end
355
+
356
+ it "should extract ##{hashtag} within text" do
357
+ match_hashtag_in_text(hashtag, "pre-text ##{hashtag} post-text", 9)
358
+ end
359
+ end
360
+ end
361
+
362
+ context "international hashtags" do
363
+ context "should allow accents" do
364
+ %w(mañana café münchen).each do |hashtag|
365
+ it "should extract ##{hashtag}" do
366
+ match_hashtag_in_text(hashtag, "##{hashtag}")
367
+ end
368
+
369
+ it "should extract ##{hashtag} within text" do
370
+ match_hashtag_in_text(hashtag, "pre-text ##{hashtag} post-text", 9)
371
+ end
372
+ end
373
+
374
+ it "should not allow the multiplication character" do
375
+ match_hashtag_in_text("pre", "#pre#{[0xd7].pack('U')}post", 0)
376
+ end
377
+
378
+ it "should not allow the division character" do
379
+ match_hashtag_in_text("pre", "#pre#{[0xf7].pack('U')}post", 0)
380
+ end
381
+ end
382
+ end
383
+
384
+ it "should not extract numeric hashtags" do
385
+ not_match_hashtag_in_text("#1234")
386
+ end
387
+
388
+ it "should extract hashtag in text with supplementary character" do
389
+ match_hashtag_in_text("hashtag", "#{[0x10400].pack('U')} #hashtag", 2)
390
+ end
391
+ end
392
+ end
@@ -0,0 +1,96 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: utf-8
6
+ require File.dirname(__FILE__) + '/spec_helper'
7
+
8
+ class TestHitHighlighter
9
+ include Twitter::TwitterText::HitHighlighter
10
+ end
11
+
12
+ describe Twitter::TwitterText::HitHighlighter do
13
+ describe "highlight" do
14
+ before do
15
+ @highlighter = TestHitHighlighter.new
16
+ end
17
+
18
+ context "with options" do
19
+ before do
20
+ @original = "Testing this hit highliter"
21
+ @hits = [[13,16]]
22
+ end
23
+
24
+ it "should default to <em> tags" do
25
+ expect(@highlighter.hit_highlight(@original, @hits)).to be == "Testing this <em>hit</em> highliter"
26
+ end
27
+
28
+ it "should allow tag override" do
29
+ expect(@highlighter.hit_highlight(@original, @hits, :tag => 'b')).to be == "Testing this <b>hit</b> highliter"
30
+ end
31
+ end
32
+
33
+ context "without links" do
34
+ before do
35
+ @original = "Hey! this is a test tweet"
36
+ end
37
+
38
+ it "should return original when no hits are provided" do
39
+ expect(@highlighter.hit_highlight(@original)).to be == @original
40
+ end
41
+
42
+ it "should highlight one hit" do
43
+ expect(@highlighter.hit_highlight(@original, hits = [[5, 9]])).to be == "Hey! <em>this</em> is a test tweet"
44
+ end
45
+
46
+ it "should highlight two hits" do
47
+ expect(@highlighter.hit_highlight(@original, hits = [[5, 9], [15, 19]])).to be == "Hey! <em>this</em> is a <em>test</em> tweet"
48
+ end
49
+
50
+ it "should correctly highlight first-word hits" do
51
+ expect(@highlighter.hit_highlight(@original, hits = [[0, 3]])).to be == "<em>Hey</em>! this is a test tweet"
52
+ end
53
+
54
+ it "should correctly highlight last-word hits" do
55
+ expect(@highlighter.hit_highlight(@original, hits = [[20, 25]])).to be == "Hey! this is a test <em>tweet</em>"
56
+ end
57
+ end
58
+
59
+ context "with links" do
60
+ it "should highlight with a single link" do
61
+ expect(@highlighter.hit_highlight("@<a>bcherry</a> this was a test tweet", [[9, 13]])).to be == "@<a>bcherry</a> <em>this</em> was a test tweet"
62
+ end
63
+
64
+ it "should highlight with link at the end" do
65
+ expect(@highlighter.hit_highlight("test test <a>test</a>", [[5, 9]])).to be == "test <em>test</em> <a>test</a>"
66
+ end
67
+
68
+ it "should highlight with a link at the beginning" do
69
+ expect(@highlighter.hit_highlight("<a>test</a> test test", [[5, 9]])).to be == "<a>test</a> <em>test</em> test"
70
+ end
71
+
72
+ it "should highlight an entire link" do
73
+ expect(@highlighter.hit_highlight("test <a>test</a> test", [[5, 9]])).to be == "test <a><em>test</em></a> test"
74
+ end
75
+
76
+ it "should highlight within a link" do
77
+ expect(@highlighter.hit_highlight("test <a>test</a> test", [[6, 8]])).to be == "test <a>t<em>es</em>t</a> test"
78
+ end
79
+
80
+ it "should highlight around a link" do
81
+ expect(@highlighter.hit_highlight("test <a>test</a> test", [[3, 11]])).to be == "tes<em>t <a>test</a> t</em>est"
82
+ end
83
+
84
+ it "should fail gracefully with bad hits" do
85
+ expect(@highlighter.hit_highlight("test test", [[5, 20]])).to be == "test <em>test</em>"
86
+ end
87
+
88
+ it "should not mess up with touching tags" do
89
+ expect(@highlighter.hit_highlight("<a>foo</a><a>foo</a>", [[3,6]])).to be == "<a>foo</a><a><em>foo</em></a>"
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end