OSDN Git Service

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