OSDN Git Service

2b9d3a32332db0d379b1d1e6174118181a3eebb7
[opentween/open-tween.git] / OpenTween.Tests / TweetFormatterTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
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)
10 // any later version.
11 //
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
15 // for more details.
16 //
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.
21
22 using System;
23 using System.Collections.Generic;
24 using System.Linq;
25 using System.Text;
26 using OpenTween.Api.DataModel;
27 using Xunit;
28 using Xunit.Extensions;
29
30 namespace OpenTween
31 {
32     public class TweetFormatterTest
33     {
34         [Fact]
35         public void FormatUrlEntity_Test()
36         {
37             var text = "http://t.co/6IwepKCM0P";
38             var entities = new[]
39             {
40                 new TwitterEntityUrl
41                 {
42                     Indices = new[] { 0, 22 },
43                     DisplayUrl = "example.com",
44                     ExpandedUrl = "http://example.com/",
45                     Url = "http://t.co/6IwepKCM0P",
46                 },
47             };
48
49             var expected = "<a href=\"http://t.co/6IwepKCM0P\" title=\"http://example.com/\">example.com</a>";
50             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
51         }
52
53         [Fact]
54         public void FormatUrlEntity_TwitterComTest()
55         {
56             var text = "https://t.co/0Ko1I27m0a";
57             var entities = new[]
58             {
59                 new TwitterEntityUrl
60                 {
61                     Indices = new[] { 0, 23 },
62                     DisplayUrl = "twitter.com/twitterapi",
63                     ExpandedUrl = "https://twitter.com/twitterapi",
64                     Url = "https://t.co/0Ko1I27m0a",
65                 },
66             };
67
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));
71         }
72
73         [Fact]
74         public void FormatHashtagEntity_Test()
75         {
76             var text = "#OpenTween";
77             var entities = new[]
78             {
79                 new TwitterEntityHashtag
80                 {
81                     Indices = new[] { 0, 10 },
82                     Text = "OpenTween",
83                 },
84             };
85
86             var expected = "<a class=\"hashtag\" href=\"https://twitter.com/search?q=%23OpenTween\">#OpenTween</a>";
87             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
88         }
89
90         [Fact]
91         public void FormatMentionEntity_Test()
92         {
93             var text = "@TwitterAPI";
94             var entities = new[]
95             {
96                 new TwitterEntityMention
97                 {
98                     Indices = new[] { 0, 11 },
99                     Id = 6253282L,
100                     Name = "Twitter API",
101                     ScreenName = "twitterapi",
102                 },
103             };
104
105             var expected = "<a class=\"mention\" href=\"https://twitter.com/twitterapi\">@TwitterAPI</a>";
106             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
107         }
108
109         [Fact]
110         public void FormatMediaEntity_Test()
111         {
112             var text = "http://t.co/h5dCr4ftN4";
113             var entities = new[]
114             {
115                 new TwitterEntityMedia
116                 {
117                     Indices = new[] { 0, 22 },
118                     Sizes = new TwitterMediaSizes
119                     {
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 },
124                     },
125                     Type = "photo",
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",
131                 },
132             };
133
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));
136         }
137
138         [Fact]
139         public void FormatMediaEntity_AltTextTest()
140         {
141             var text = "http://t.co/h5dCr4ftN4";
142             var entities = new[]
143             {
144                 new TwitterEntityMedia
145                 {
146                     Indices = new[] { 0, 22 },
147                     Sizes = new TwitterMediaSizes
148                     {
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 },
153                     },
154                     Type = "photo",
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の推測変換候補のスクリーンショット",
161                 },
162             };
163
164             var expected = "<a href=\"http://t.co/h5dCr4ftN4\" title=\"ATOKの推測変換候補のスクリーンショット\">pic.twitter.com/h5dCr4ftN4</a>";
165             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
166         }
167
168
169         [Fact]
170         public void FormatEmojiEntity_Test()
171         {
172             var text = "🍣";
173             var entities = new[]
174             {
175                 new TwitterEntityEmoji
176                 {
177                     Indices = new[] { 0, 1 },
178                     Text = "🍣",
179                     Url = "https://twemoji.maxcdn.com/2/72x72/1f363.png",
180                 },
181             };
182
183             var expected = "<img class=\"emoji\" src=\"https://twemoji.maxcdn.com/2/72x72/1f363.png\" alt=\"🍣\" />";
184             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
185         }
186         [Fact]
187         public void AutoLinkHtml_EntityNullTest()
188         {
189             var text = "てすとてすとー";
190             TwitterEntities entities = null;
191
192             var expected = "てすとてすとー";
193             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
194         }
195
196         [Fact]
197         public void AutoLinkHtml_EntityNullTest2()
198         {
199             var text = "てすとてすとー";
200             TwitterEntities entities = new TwitterEntities
201             {
202                 Urls = null,
203                 Hashtags = null,
204                 UserMentions = null,
205                 Media = null,
206             };
207
208             var expected = "てすとてすとー";
209             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
210         }
211
212         [Fact]
213         public void AutoLinkHtml_EntityNullTest3()
214         {
215             var text = "てすとてすとー";
216             IEnumerable<TwitterEntity> entities = null;
217
218             var expected = "てすとてすとー";
219             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
220         }
221
222         [Fact]
223         public void AutoLinkHtml_EntityNullTest4()
224         {
225             var text = "てすとてすとー";
226             IEnumerable<TwitterEntity> entities = new TwitterEntity[] { null };
227
228             var expected = "てすとてすとー";
229             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
230         }
231
232         [Fact]
233         public void AutoLinkHtml_EscapeTest()
234         {
235             // Twitter APIの中途半端なエスケープの対象とならない「"」や「'」に対するエスケープ処理を施す
236             var text = "\"\'@twitterapi\'\"";
237             var entities = new[]
238             {
239                 new TwitterEntityMention
240                 {
241                     Indices = new[] { 2, 13 },
242                     Id = 6253282L,
243                     Name = "Twitter API",
244                     ScreenName = "twitterapi",
245                 },
246             };
247
248             var expected = "&quot;&#39;<a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a>&#39;&quot;";
249             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
250         }
251
252         [Fact]
253         public void AutoLinkHtml_EscapeTest2()
254         {
255             // 「<」や「>」についてはエスケープされた状態でAPIからテキストが返されるため、二重エスケープとならないように考慮する
256             var text = "&lt;b&gt; @twitterapi &lt;/b&gt;";
257             var entities = new[]
258             {
259                 new TwitterEntityMention
260                 {
261                     Indices = new[] { 10, 21 },
262                     Id = 6253282L,
263                     Name = "Twitter API",
264                     ScreenName = "twitterapi",
265                 },
266             };
267
268             var expected = "&lt;b&gt; <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> &lt;/b&gt;";
269             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
270         }
271
272         [Fact]
273         public void AutoLinkHtml_EscapeTest3()
274         {
275             // 万が一「<」や「>」がエスケープされていない状態のテキストを受け取っても適切にエスケープが施されるようにする
276             var text = "<b> @twitterapi </b>";
277             var entities = new[]
278             {
279                 new TwitterEntityMention
280                 {
281                     Indices = new[] { 4, 15 },
282                     Id = 6253282L,
283                     Name = "Twitter API",
284                     ScreenName = "twitterapi",
285                 },
286             };
287
288             var expected = "&lt;b&gt; <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> &lt;/b&gt;";
289             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
290         }
291
292         [Fact]
293         public void AutoLinkHtml_EscapeUrlTest()
294         {
295             // 日本語ハッシュタグのリンク先URLを適切にエスケープする
296             var text = "#ぜんぶ雪のせいだ";
297             var entities = new[]
298             {
299                 new TwitterEntityHashtag
300                 {
301                     Indices = new[] { 0, 9 },
302                     Text = "ぜんぶ雪のせいだ",
303                 },
304             };
305
306             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>";
307             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
308         }
309
310         [Fact]
311         public void AutoLinkHtml_SurrogatePairTest()
312         {
313             // UTF-16 で 4 バイトで表される文字を含むツイート
314             // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33079
315             var text = "🐬🐬 @irucame 🐬🐬";
316             var entities = new[]
317             {
318                 new TwitterEntityMention
319                 {
320                     Indices = new[] { 3, 11 },
321                     Id = 89942943L,
322                     ScreenName = "irucame",
323                 },
324             };
325
326             var expected = "🐬🐬 <a class=\"mention\" href=\"https://twitter.com/irucame\">@irucame</a> 🐬🐬";
327             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
328         }
329
330         [Fact]
331         public void AutoLinkHtml_SurrogatePairTest2()
332         {
333             // 現時点では存在しないものの、ハッシュタグなどエンティティ内にサロゲートペアが含まれる場合も考慮する
334             var text = "🐬🐬 #🐬🐬 🐬🐬 #🐬🐬 🐬🐬";
335             var entities = new[]
336             {
337                 new TwitterEntityHashtag
338                 {
339                     Indices = new[] { 3, 6 },
340                     Text = "🐬🐬",
341                 },
342                 new TwitterEntityHashtag
343                 {
344                     Indices = new[] { 10, 13 },
345                     Text = "🐬🐬",
346                 },
347             };
348
349             var expected = "🐬🐬 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23%F0%9F%90%AC%F0%9F%90%AC\">#🐬🐬</a> " +
350                 "🐬🐬 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23%F0%9F%90%AC%F0%9F%90%AC\">#🐬🐬</a> 🐬🐬";
351             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
352         }
353
354         [Fact]
355         public void AutoLinkHtml_CompositeCharacterTest()
356         {
357             // 合成文字 é ( \u00e9 ) を含むツイート
358             // 参照: https://dev.twitter.com/issues/251
359             var text = "Caf\u00e9 #test";
360             var entities = new[]
361             {
362                 new TwitterEntityHashtag
363                 {
364                     Indices = new[] { 5, 10 },
365                     Text = "test",
366                 },
367             };
368
369             var expected = "Caf\u00e9 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23test\">#test</a>";
370             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
371         }
372
373         [Fact]
374         public void AutoLinkHtml_CombiningCharacterSequenceTest()
375         {
376             // 結合文字列 é ( e + \u0301 ) を含むツイート
377             // 参照: https://dev.twitter.com/issues/251
378             var text = "Cafe\u0301 #test";
379             var entities = new[]
380             {
381                 new TwitterEntityHashtag
382                 {
383                     Indices = new[] { 6, 11 },
384                     Text = "test",
385                 },
386             };
387
388             var expected = "Cafe\u0301 <a class=\"hashtag\" href=\"https://twitter.com/search?q=%23test\">#test</a>";
389             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
390         }
391
392         [Fact]
393         public void AutoLinkHtml_BreakLineTest()
394         {
395             var text = "てすと\nてすと\nてすと";
396             TwitterEntities entities = null;
397
398             var expected = "てすと<br>てすと<br>てすと";
399             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
400         }
401
402         [Fact]
403         public void AutoLinkHtml_WhitespaceTest()
404         {
405             // 連続する半角スペースを表示するため 1 文字おきに &nbsp; に変換する
406             var text = "a a  a   a    a";
407
408             var expected = "a a &nbsp;a &nbsp; a &nbsp; &nbsp;a";
409             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities: null));
410         }
411
412         [Fact]
413         public void AutoLinkHtml_OverlappedEntitiesTest()
414         {
415             // extended_entities で追加される、区間が重複したエンティティを考慮
416             // 参照: https://dev.twitter.com/docs/api/multiple-media-extended-entities
417
418             var text = "\"I hope you'll keep...building bonds of friendship that will enrich your lives &amp; enrich our world\" \u2014FLOTUS in China, http://t.co/fxmuQN9JL9";
419             var entities = new[]
420             {
421                 new TwitterEntityMedia
422                 {
423                     DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
424                     ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
425                     Indices = new[] { 121, 143 },
426                     MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EH6yIQAEYvxu.jpg",
427                     Url = "http://t.co/fxmuQN9JL9",
428                 },
429                 new TwitterEntityMedia
430                 {
431                     DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
432                     ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
433                     Indices = new[] { 121, 143 },
434                     MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EHxAIIAE8dtg.jpg",
435                     Url = "http://t.co/fxmuQN9JL9",
436                 },
437                 new TwitterEntityMedia
438                 {
439                     DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
440                     ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
441                     Indices = new[] { 121, 143 },
442                     MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EH3pIYAE4LQn.jpg",
443                     Url = "http://t.co/fxmuQN9JL9",
444                 },
445                 new TwitterEntityMedia
446                 {
447                     DisplayUrl = "pic.twitter.com/fxmuQN9JL9",
448                     ExpandedUrl = "http://twitter.com/FLOTUS/status/449660889793581056/photo/1",
449                     Indices = new[] { 121, 143 },
450                     MediaUrlHttps = "https://pbs.twimg.com/media/Bj2EL3DIEAAzGAX.jpg",
451                     Url = "http://t.co/fxmuQN9JL9",
452                 },
453             };
454
455             var expected = "&quot;I hope you&#39;ll keep...building bonds of friendship that will enrich your lives &amp; enrich our world&quot; \u2014FLOTUS in China, " +
456                 "<a href=\"http://t.co/fxmuQN9JL9\" title=\"http://twitter.com/FLOTUS/status/449660889793581056/photo/1\">pic.twitter.com/fxmuQN9JL9</a>";
457             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
458         }
459     }
460 }