OSDN Git Service

OpenTween v2.4.2 リリース
[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         [Fact]
169         public void FormatEmojiEntity_Test()
170         {
171             var text = "🍣";
172             var entities = new[]
173             {
174                 new TwitterEntityEmoji
175                 {
176                     Indices = new[] { 0, 1 },
177                     Text = "🍣",
178                     Url = "https://twemoji.maxcdn.com/2/72x72/1f363.png",
179                 },
180             };
181
182             var expected = "<img class=\"emoji\" src=\"https://twemoji.maxcdn.com/2/72x72/1f363.png\" alt=\"🍣\" />";
183             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
184         }
185
186         [Fact]
187         public void FormatEmojiEntity_EmptyUrlTest()
188         {
189             // 余分な U+FE0F があった場合に Url が空の絵文字エンティティが渡される
190             var text = "\uFE0F";
191             var entities = new[]
192             {
193                 new TwitterEntityEmoji
194                 {
195                     Indices = new[] { 0, 1 },
196                     Text = "",
197                     Url = "",
198                 },
199             };
200
201             var expected = "";
202             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
203         }
204
205         [Fact]
206         public void AutoLinkHtml_EntityNullTest()
207         {
208             var text = "てすとてすとー";
209             TwitterEntities? entities = null;
210
211             var expected = "てすとてすとー";
212             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
213         }
214
215         [Fact]
216         public void AutoLinkHtml_EntityNullTest2()
217         {
218             var text = "てすとてすとー";
219             var entities = new TwitterEntities
220             {
221                 Urls = null,
222                 Hashtags = null,
223                 UserMentions = null,
224                 Media = null,
225             };
226
227             var expected = "てすとてすとー";
228             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
229         }
230
231         [Fact]
232         public void AutoLinkHtml_EntityNullTest3()
233         {
234             var text = "てすとてすとー";
235             IEnumerable<TwitterEntity>? entities = null;
236
237             var expected = "てすとてすとー";
238             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
239         }
240
241         [Fact]
242         public void AutoLinkHtml_EscapeTest()
243         {
244             // Twitter APIの中途半端なエスケープの対象とならない「"」や「'」に対するエスケープ処理を施す
245             var text = "\"\'@twitterapi\'\"";
246             var entities = new[]
247             {
248                 new TwitterEntityMention
249                 {
250                     Indices = new[] { 2, 13 },
251                     Id = 6253282L,
252                     Name = "Twitter API",
253                     ScreenName = "twitterapi",
254                 },
255             };
256
257             var expected = "&quot;&#39;<a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a>&#39;&quot;";
258             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
259         }
260
261         [Fact]
262         public void AutoLinkHtml_EscapeTest2()
263         {
264             // 「<」や「>」についてはエスケープされた状態でAPIからテキストが返されるため、二重エスケープとならないように考慮する
265             var text = "&lt;b&gt; @twitterapi &lt;/b&gt;";
266             var entities = new[]
267             {
268                 new TwitterEntityMention
269                 {
270                     Indices = new[] { 10, 21 },
271                     Id = 6253282L,
272                     Name = "Twitter API",
273                     ScreenName = "twitterapi",
274                 },
275             };
276
277             var expected = "&lt;b&gt; <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> &lt;/b&gt;";
278             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
279         }
280
281         [Fact]
282         public void AutoLinkHtml_EscapeTest3()
283         {
284             // 万が一「<」や「>」がエスケープされていない状態のテキストを受け取っても適切にエスケープが施されるようにする
285             var text = "<b> @twitterapi </b>";
286             var entities = new[]
287             {
288                 new TwitterEntityMention
289                 {
290                     Indices = new[] { 4, 15 },
291                     Id = 6253282L,
292                     Name = "Twitter API",
293                     ScreenName = "twitterapi",
294                 },
295             };
296
297             var expected = "&lt;b&gt; <a class=\"mention\" href=\"https://twitter.com/twitterapi\">@twitterapi</a> &lt;/b&gt;";
298             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
299         }
300
301         [Fact]
302         public void AutoLinkHtml_EscapeUrlTest()
303         {
304             // 日本語ハッシュタグのリンク先URLを適切にエスケープする
305             var text = "#ぜんぶ雪のせいだ";
306             var entities = new[]
307             {
308                 new TwitterEntityHashtag
309                 {
310                     Indices = new[] { 0, 9 },
311                     Text = "ぜんぶ雪のせいだ",
312                 },
313             };
314
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));
317         }
318
319         [Fact]
320         public void AutoLinkHtml_SurrogatePairTest()
321         {
322             // UTF-16 で 4 バイトで表される文字を含むツイート
323             // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33079
324             var text = "🐬🐬 @irucame 🐬🐬";
325             var entities = new[]
326             {
327                 new TwitterEntityMention
328                 {
329                     Indices = new[] { 3, 11 },
330                     Id = 89942943L,
331                     ScreenName = "irucame",
332                 },
333             };
334
335             var expected = "🐬🐬 <a class=\"mention\" href=\"https://twitter.com/irucame\">@irucame</a> 🐬🐬";
336             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
337         }
338
339         [Fact]
340         public void AutoLinkHtml_SurrogatePairTest2()
341         {
342             // 現時点では存在しないものの、ハッシュタグなどエンティティ内にサロゲートペアが含まれる場合も考慮する
343             var text = "🐬🐬 #🐬🐬 🐬🐬 #🐬🐬 🐬🐬";
344             var entities = new[]
345             {
346                 new TwitterEntityHashtag
347                 {
348                     Indices = new[] { 3, 6 },
349                     Text = "🐬🐬",
350                 },
351                 new TwitterEntityHashtag
352                 {
353                     Indices = new[] { 10, 13 },
354                     Text = "🐬🐬",
355                 },
356             };
357
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));
361         }
362
363         [Fact]
364         public void AutoLinkHtml_CompositeCharacterTest()
365         {
366             // 合成文字 é ( \u00e9 ) を含むツイート
367             // 参照: https://dev.twitter.com/issues/251
368             var text = "Caf\u00e9 #test";
369             var entities = new[]
370             {
371                 new TwitterEntityHashtag
372                 {
373                     Indices = new[] { 5, 10 },
374                     Text = "test",
375                 },
376             };
377
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));
380         }
381
382         [Fact]
383         public void AutoLinkHtml_CombiningCharacterSequenceTest()
384         {
385             // 結合文字列 é ( e + \u0301 ) を含むツイート
386             // 参照: https://dev.twitter.com/issues/251
387             var text = "Cafe\u0301 #test";
388             var entities = new[]
389             {
390                 new TwitterEntityHashtag
391                 {
392                     Indices = new[] { 6, 11 },
393                     Text = "test",
394                 },
395             };
396
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));
399         }
400
401         [Fact]
402         public void AutoLinkHtml_BreakLineTest()
403         {
404             var text = "てすと\nてすと\nてすと";
405             TwitterEntities? entities = null;
406
407             var expected = "てすと<br>てすと<br>てすと";
408             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities));
409         }
410
411         [Fact]
412         public void AutoLinkHtml_WhitespaceTest()
413         {
414             // 連続する半角スペースを表示するため 1 文字おきに &nbsp; に変換する
415             var text = "a a  a   a    a";
416
417             var expected = "a a &nbsp;a &nbsp; a &nbsp; &nbsp;a";
418             Assert.Equal(expected, TweetFormatter.AutoLinkHtml(text, entities: null));
419         }
420
421         [Fact]
422         public void AutoLinkHtml_OverlappedEntitiesTest()
423         {
424             // extended_entities で追加される、区間が重複したエンティティを考慮
425             // 参照: https://dev.twitter.com/docs/api/multiple-media-extended-entities
426
427             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";
428             var entities = new[]
429             {
430                 new TwitterEntityMedia
431                 {
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",
437                 },
438                 new TwitterEntityMedia
439                 {
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",
445                 },
446                 new TwitterEntityMedia
447                 {
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",
453                 },
454                 new TwitterEntityMedia
455                 {
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",
461                 },
462             };
463
464             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, " +
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));
467         }
468     }
469 }