1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
23 using System.Collections.Generic;
26 using OpenTween.Api.DataModel;
28 using Xunit.Extensions;
32 public class TweetFormatterTest
35 public void FormatUrlEntity_Test()
37 var text = "http://t.co/6IwepKCM0P";
42 Indices = new[] { 0, 22 },
43 DisplayUrl = "example.com",
44 ExpandedUrl = "http://example.com/",
45 Url = "http://t.co/6IwepKCM0P",
49 var expected = "<a href=\"http://t.co/6IwepKCM0P\" title=\"http://example.com/\">example.com</a>";
50 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
54 public void FormatUrlEntity_TwitterComTest()
56 var text = "https://t.co/0Ko1I27m0a";
61 Indices = new[] { 0, 23 },
62 DisplayUrl = "twitter.com/twitterapi",
63 ExpandedUrl = "https://twitter.com/twitterapi",
64 Url = "https://t.co/0Ko1I27m0a",
68 // twitter.com 宛のリンクは t.co を経由せずにリンクする
69 var expected = "<a href=\"https://twitter.com/twitterapi\" title=\"https://twitter.com/twitterapi\">twitter.com/twitterapi</a>";
70 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
74 public void FormatHashtagEntity_Test()
76 var text = "#OpenTween";
79 new TwitterEntityHashtag
81 Indices = new[] { 0, 10 },
86 var expected = "<a class=\"hashtag\" href=\"https://twitter.com/search?q=%23OpenTween\">#OpenTween</a>";
87 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
91 public void FormatMentionEntity_Test()
93 var text = "@TwitterAPI";
96 new TwitterEntityMention
98 Indices = new[] { 0, 11 },
100 Name = "Twitter API",
101 ScreenName = "twitterapi",
105 var expected = "<a class=\"mention\" href=\"https://twitter.com/twitterapi\">@TwitterAPI</a>";
106 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
110 public void FormatMediaEntity_Test()
112 var text = "http://t.co/h5dCr4ftN4";
115 new TwitterEntityMedia
117 Indices = new[] { 0, 22 },
118 Sizes = new TwitterMediaSizes
120 Large = new TwitterMediaSizes.Size { Resize = "fit", Height = 329, Width = 1024 },
121 Medium = new TwitterMediaSizes.Size { Resize = "fit", Height = 204, Width = 600 },
122 Small = new TwitterMediaSizes.Size { Resize = "fit", Height = 116, Width = 340 },
123 Thumb = new TwitterMediaSizes.Size { Resize = "crop", Height = 150, Width = 150 },
126 Id = 426404550379986940L,
127 MediaUrlHttps = "https://pbs.twimg.com/media/BerkrewCYAAV4Kf.png",
128 Url = "http://t.co/h5dCr4ftN4",
129 DisplayUrl = "pic.twitter.com/h5dCr4ftN4",
130 ExpandedUrl = "http://twitter.com/kim_upsilon/status/426404550371598337/photo/1",
134 var expected = "<a href=\"http://t.co/h5dCr4ftN4\" title=\"http://twitter.com/kim_upsilon/status/426404550371598337/photo/1\">pic.twitter.com/h5dCr4ftN4</a>";
135 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
139 public void FormatMediaEntity_AltTextTest()
141 var text = "http://t.co/h5dCr4ftN4";
144 new TwitterEntityMedia
146 Indices = new[] { 0, 22 },
147 Sizes = new TwitterMediaSizes
149 Large = new TwitterMediaSizes.Size { Resize = "fit", Height = 329, Width = 1024 },
150 Medium = new TwitterMediaSizes.Size { Resize = "fit", Height = 204, Width = 600 },
151 Small = new TwitterMediaSizes.Size { Resize = "fit", Height = 116, Width = 340 },
152 Thumb = new TwitterMediaSizes.Size { Resize = "crop", Height = 150, Width = 150 },
155 Id = 426404550379986940L,
156 MediaUrlHttps = "https://pbs.twimg.com/media/BerkrewCYAAV4Kf.png",
157 Url = "http://t.co/h5dCr4ftN4",
158 DisplayUrl = "pic.twitter.com/h5dCr4ftN4",
159 ExpandedUrl = "http://twitter.com/kim_upsilon/status/426404550371598337/photo/1",
160 AltText = "ATOKの推測変換候補のスクリーンショット",
164 var expected = "<a href=\"http://t.co/h5dCr4ftN4\" title=\"ATOKの推測変換候補のスクリーンショット\">pic.twitter.com/h5dCr4ftN4</a>";
165 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
169 public void FormatEmojiEntity_Test()
174 new TwitterEntityEmoji
176 Indices = new[] { 0, 1 },
178 Url = "https://twemoji.maxcdn.com/2/72x72/1f363.png",
182 var expected = "<img class=\"emoji\" src=\"https://twemoji.maxcdn.com/2/72x72/1f363.png\" alt=\"🍣\" />";
183 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
187 public void FormatEmojiEntity_EmptyUrlTest()
189 // 余分な U+FE0F があった場合に Url が空の絵文字エンティティが渡される
193 new TwitterEntityEmoji
195 Indices = new[] { 0, 1 },
202 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
206 public void AutoLinkHtml_EntityNullTest()
208 var text = "てすとてすとー";
209 TwitterEntities? entities = null;
211 var expected = "てすとてすとー";
212 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
216 public void AutoLinkHtml_EntityNullTest2()
218 var text = "てすとてすとー";
219 var entities = new TwitterEntities
227 var expected = "てすとてすとー";
228 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
232 public void AutoLinkHtml_EntityNullTest3()
234 var text = "てすとてすとー";
235 IEnumerable<TwitterEntity>? entities = null;
237 var expected = "てすとてすとー";
238 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
242 public void AutoLinkHtml_EscapeTest()
244 // Twitter APIの中途半端なエスケープの対象とならない「"」や「'」に対するエスケープ処理を施す
245 var text = "\"\'@twitterapi\'\"";
248 new TwitterEntityMention
250 Indices = new[] { 2, 13 },
252 Name = "Twitter API",
253 ScreenName = "twitterapi",
257 var expected = ""'<a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a>'"";
258 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
262 public void AutoLinkHtml_EscapeTest2()
264 // 「<」や「>」についてはエスケープされた状態でAPIからテキストが返されるため、二重エスケープとならないように考慮する
265 var text = "<b> @twitterapi </b>";
268 new TwitterEntityMention
270 Indices = new[] { 10, 21 },
272 Name = "Twitter API",
273 ScreenName = "twitterapi",
277 var expected = "<b> <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> </b>";
278 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
282 public void AutoLinkHtml_EscapeTest3()
284 // 万が一「<」や「>」がエスケープされていない状態のテキストを受け取っても適切にエスケープが施されるようにする
285 var text = "<b> @twitterapi </b>";
288 new TwitterEntityMention
290 Indices = new[] { 4, 15 },
292 Name = "Twitter API",
293 ScreenName = "twitterapi",
297 var expected = "<b> <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> </b>";
298 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
302 public void AutoLinkHtml_EscapeUrlTest()
304 // 日本語ハッシュタグのリンク先URLを適切にエスケープする
305 var text = "#ぜんぶ雪のせいだ";
308 new TwitterEntityHashtag
310 Indices = new[] { 0, 9 },
315 var expected = "<a class=\"hashtag\" href=\"https://twitter.com/search?q=%23%E3%81%9C%E3%82%93%E3%81%B6%E9%9B%AA%E3%81%AE%E3%81%9B%E3%81%84%E3%81%A0\">#ぜんぶ雪のせいだ</a>";
316 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
320 public void AutoLinkHtml_SurrogatePairTest()
322 // UTF-16 で 4 バイトで表される文字を含むツイート
323 // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33079
324 var text = "🐬🐬 @irucame 🐬🐬";
327 new TwitterEntityMention
329 Indices = new[] { 3, 11 },
331 ScreenName = "irucame",
335 var expected = "🐬🐬 <a class=\"mention\" href=\"https://twitter.com/irucame\">@irucame</a> 🐬🐬";
336 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
340 public void AutoLinkHtml_SurrogatePairTest2()
342 // 現時点では存在しないものの、ハッシュタグなどエンティティ内にサロゲートペアが含まれる場合も考慮する
343 var text = "🐬🐬 #🐬🐬 🐬🐬 #🐬🐬 🐬🐬";
346 new TwitterEntityHashtag
348 Indices = new[] { 3, 6 },
351 new TwitterEntityHashtag
353 Indices = new[] { 10, 13 },
358 var expected = "🐬🐬 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23%F0%9F%90%AC%F0%9F%90%AC\">#🐬🐬</a> " +
359 "🐬🐬 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23%F0%9F%90%AC%F0%9F%90%AC\">#🐬🐬</a> 🐬🐬";
360 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
364 public void AutoLinkHtml_CompositeCharacterTest()
366 // 合成文字 é ( \u00e9 ) を含むツイート
367 // 参照: https://dev.twitter.com/issues/251
368 var text = "Caf\u00e9 #test";
371 new TwitterEntityHashtag
373 Indices = new[] { 5, 10 },
378 var expected = "Caf\u00e9 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23test\">#test</a>";
379 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
383 public void AutoLinkHtml_CombiningCharacterSequenceTest()
385 // 結合文字列 é ( e + \u0301 ) を含むツイート
386 // 参照: https://dev.twitter.com/issues/251
387 var text = "Cafe\u0301 #test";
390 new TwitterEntityHashtag
392 Indices = new[] { 6, 11 },
397 var expected = "Cafe\u0301 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23test\">#test</a>";
398 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
402 public void AutoLinkHtml_BreakLineTest()
404 var text = "てすと\nてすと\nてすと";
405 TwitterEntities? entities = null;
407 var expected = "てすと<br>てすと<br>てすと";
408 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
412 public void AutoLinkHtml_WhitespaceTest()
414 // 連続する半角スペースを表示するため 1 文字おきに に変換する
415 var text = "a a a a a";
417 var expected = "a a a a a";
418 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities: null));
422 public void AutoLinkHtml_OverlappedEntitiesTest()
424 // extended_entities で追加される、区間が重複したエンティティを考慮
425 // 参照: https://dev.twitter.com/docs/api/multiple-media-extended-entities
427 var text = "\"I hope you'll keep...building bonds of friendship that will enrich your lives & enrich our world\" \u2014FLOTUS in China, http://t.co/fxmuQN9JL9";
430 new TwitterEntityMedia
432 DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
433 ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
434 Indices = new[] { 121, 143 },
435 MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EH6yIQAEYvxu.jpg",
436 Url = "http://t.co/fxmuQN9JL9",
438 new TwitterEntityMedia
440 DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
441 ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
442 Indices = new[] { 121, 143 },
443 MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EHxAIIAE8dtg.jpg",
444 Url = "http://t.co/fxmuQN9JL9",
446 new TwitterEntityMedia
448 DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
449 ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
450 Indices = new[] { 121, 143 },
451 MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EH3pIYAE4LQn.jpg",
452 Url = "http://t.co/fxmuQN9JL9",
454 new TwitterEntityMedia
456 DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
457 ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
458 Indices = new[] { 121, 143 },
459 MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EL3DIEAAzGAX.jpg",
460 Url = "http://t.co/fxmuQN9JL9",
464 var expected = ""I hope you'll keep...building bonds of friendship that will enrich your lives & enrich our world" \u2014FLOTUS in China, " +
465 "<a href=\"http://t.co/fxmuQN9JL9\" title=\"http://twitter.com/FLOTUS/status/449660889793581056/photo/1\">pic.twitter.com/fxmuQN9JL9</a>";
466 Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));