OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[opentween/open-tween.git] / OpenTween.Tests / TwitterTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2013 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 System.Text.RegularExpressions;
27 using OpenTween.Api.DataModel;
28 using OpenTween.Models;
29 using OpenTween.Setting;
30 using Xunit;
31 using Xunit.Extensions;
32
33 namespace OpenTween
34 {
35     public class TwitterTest
36     {
37         [Theory]
38         [InlineData("https://twitter.com/twitterapi/status/22634515958",
39             new[] { "22634515958" })]
40         [InlineData("<a target=\"_self\" href=\"https://t.co/aaaaaaaa\" title=\"https://twitter.com/twitterapi/status/22634515958\">twitter.com/twitterapi/stat…</a>",
41             new[] { "22634515958" })]
42         [InlineData("<a target=\"_self\" href=\"https://t.co/bU3oR95KIy\" title=\"https://twitter.com/haru067/status/224782458816692224\">https://t.co/bU3oR95KIy</a>" +
43             "<a target=\"_self\" href=\"https://t.co/bbbbbbbb\" title=\"https://twitter.com/karno/status/311081657790771200\">https://t.co/bbbbbbbb</a>",
44             new[] { "224782458816692224", "311081657790771200" })]
45         [InlineData("https://mobile.twitter.com/muji_net/status/21984934471",
46             new[] { "21984934471" })]
47         [InlineData("https://twitter.com/imgazyobuzi/status/293333871171354624/photo/1",
48             new[] { "293333871171354624" })]
49         public void StatusUrlRegexTest(string url, string[] expected)
50         {
51             var results = Twitter.StatusUrlRegex.Matches(url).Cast<Match>()
52                 .Select(x => x.Groups["StatusId"].Value).ToArray();
53
54             Assert.Equal(expected, results);
55         }
56
57         [Theory]
58         [InlineData("https://twitter.com/twitterapi/status/22634515958", true)]
59         [InlineData("http://twitter.com/twitterapi/status/22634515958", true)]
60         [InlineData("https://mobile.twitter.com/twitterapi/status/22634515958", true)]
61         [InlineData("http://mobile.twitter.com/twitterapi/status/22634515958", true)]
62         [InlineData("https://twitter.com/i/web/status/22634515958", false)]
63         [InlineData("https://twitter.com/imgazyobuzi/status/293333871171354624/photo/1", false)]
64         [InlineData("https://pic.twitter.com/gbxdb2Oj", false)]
65         [InlineData("https://twitter.com/messages/compose?recipient_id=514241801", true)]
66         [InlineData("http://twitter.com/messages/compose?recipient_id=514241801", true)]
67         [InlineData("https://twitter.com/messages/compose?recipient_id=514241801&text=%E3%81%BB%E3%81%92", true)]
68         public void AttachmentUrlRegexTest(string url, bool isMatch)
69             => Assert.Equal(isMatch, Twitter.AttachmentUrlRegex.IsMatch(url));
70
71         [Theory]
72         [InlineData("http://favstar.fm/users/twitterapi/status/22634515958", new[] { "22634515958" })]
73         [InlineData("http://ja.favstar.fm/users/twitterapi/status/22634515958", new[] { "22634515958" })]
74         [InlineData("http://favstar.fm/t/22634515958", new[] { "22634515958" })]
75         [InlineData("http://aclog.koba789.com/i/312485321239564288", new[] { "312485321239564288" })]
76         [InlineData("http://frtrt.net/solo_status.php?status=263483634307198977", new[] { "263483634307198977" })]
77         public void ThirdPartyStatusUrlRegexTest(string url, string[] expected)
78         {
79             var results = Twitter.ThirdPartyStatusUrlRegex.Matches(url).Cast<Match>()
80                 .Select(x => x.Groups["StatusId"].Value).ToArray();
81
82             Assert.Equal(expected, results);
83         }
84
85         [Fact]
86         public void FindTopOfReplyChainTest()
87         {
88             var posts = new Dictionary<long, PostClass>
89             {
90                 [950L] = new PostClass { StatusId = 950L, InReplyToStatusId = null }, // このツイートが末端
91                 [987L] = new PostClass { StatusId = 987L, InReplyToStatusId = 950L },
92                 [999L] = new PostClass { StatusId = 999L, InReplyToStatusId = 987L },
93                 [1000L] = new PostClass { StatusId = 1000L, InReplyToStatusId = 999L },
94             };
95             Assert.Equal(950L, Twitter.FindTopOfReplyChain(posts, 1000L).StatusId);
96             Assert.Equal(950L, Twitter.FindTopOfReplyChain(posts, 950L).StatusId);
97             Assert.Throws<ArgumentException>(() => Twitter.FindTopOfReplyChain(posts, 500L));
98
99             posts = new Dictionary<long, PostClass>
100             {
101                 // 1200L は posts の中に存在しない
102                 [1210L] = new PostClass { StatusId = 1210L, InReplyToStatusId = 1200L },
103                 [1220L] = new PostClass { StatusId = 1220L, InReplyToStatusId = 1210L },
104                 [1230L] = new PostClass { StatusId = 1230L, InReplyToStatusId = 1220L },
105             };
106             Assert.Equal(1210L, Twitter.FindTopOfReplyChain(posts, 1230L).StatusId);
107             Assert.Equal(1210L, Twitter.FindTopOfReplyChain(posts, 1210L).StatusId);
108         }
109
110         [Fact]
111         public void CreateAccessibleText_MediaAltTest()
112         {
113             var text = "https://t.co/hoge";
114             var entities = new TwitterEntities
115             {
116                 Media = new[]
117                 {
118                     new TwitterEntityMedia
119                     {
120                         Indices = new[] { 0, 17 },
121                         Url = "https://t.co/hoge",
122                         DisplayUrl = "pic.twitter.com/hoge",
123                         ExpandedUrl = "https://twitter.com/hoge/status/1234567890/photo/1",
124                         AltText = "代替テキスト",
125                     },
126                 },
127             };
128
129             var expectedText = string.Format(Properties.Resources.ImageAltText, "代替テキスト");
130
131             Assert.Equal(expectedText, Twitter.CreateAccessibleText(text, entities, quotedStatus: null, quotedStatusLink: null));
132         }
133
134         [Fact]
135         public void CreateAccessibleText_MediaNoAltTest()
136         {
137             var text = "https://t.co/hoge";
138             var entities = new TwitterEntities
139             {
140                 Media = new[]
141                 {
142                     new TwitterEntityMedia
143                     {
144                         Indices = new[] { 0, 17 },
145                         Url = "https://t.co/hoge",
146                         DisplayUrl = "pic.twitter.com/hoge",
147                         ExpandedUrl = "https://twitter.com/hoge/status/1234567890/photo/1",
148                         AltText = null,
149                     },
150                 },
151             };
152
153             var expectedText = "pic.twitter.com/hoge";
154
155             Assert.Equal(expectedText, Twitter.CreateAccessibleText(text, entities, quotedStatus: null, quotedStatusLink: null));
156         }
157
158         [Fact]
159         public void CreateAccessibleText_QuotedUrlTest()
160         {
161             var text = "https://t.co/hoge";
162             var entities = new TwitterEntities
163             {
164                 Urls = new[]
165                 {
166                     new TwitterEntityUrl
167                     {
168                         Indices = new[] { 0, 17 },
169                         Url = "https://t.co/hoge",
170                         DisplayUrl = "twitter.com/hoge/status/1…",
171                         ExpandedUrl = "https://twitter.com/hoge/status/1234567890",
172                     },
173                 },
174             };
175             var quotedStatus = new TwitterStatus
176             {
177                 Id = 1234567890L,
178                 IdStr = "1234567890",
179                 User = new TwitterUser
180                 {
181                     Id = 1111,
182                     IdStr = "1111",
183                     ScreenName = "foo",
184                 },
185                 FullText = "test",
186             };
187
188             var expectedText = string.Format(Properties.Resources.QuoteStatus_AccessibleText, "foo", "test");
189
190             Assert.Equal(expectedText, Twitter.CreateAccessibleText(text, entities, quotedStatus, quotedStatusLink: null));
191         }
192
193         [Fact]
194         public void CreateAccessibleText_QuotedUrlWithPermelinkTest()
195         {
196             var text = "hoge";
197             var entities = new TwitterEntities();
198             var quotedStatus = new TwitterStatus
199             {
200                 Id = 1234567890L,
201                 IdStr = "1234567890",
202                 User = new TwitterUser
203                 {
204                     Id = 1111,
205                     IdStr = "1111",
206                     ScreenName = "foo",
207                 },
208                 FullText = "test",
209             };
210             var quotedStatusLink = new TwitterQuotedStatusPermalink
211             {
212                 Url = "https://t.co/hoge",
213                 Display = "twitter.com/hoge/status/1…",
214                 Expanded = "https://twitter.com/hoge/status/1234567890",
215             };
216
217             var expectedText = "hoge " + string.Format(Properties.Resources.QuoteStatus_AccessibleText, "foo", "test");
218
219             Assert.Equal(expectedText, Twitter.CreateAccessibleText(text, entities, quotedStatus, quotedStatusLink));
220         }
221
222         [Fact]
223         public void CreateAccessibleText_QuotedUrlNoReferenceTest()
224         {
225             var text = "https://t.co/hoge";
226             var entities = new TwitterEntities
227             {
228                 Urls = new[]
229                 {
230                     new TwitterEntityUrl
231                     {
232                         Indices = new[] { 0, 17 },
233                         Url = "https://t.co/hoge",
234                         DisplayUrl = "twitter.com/hoge/status/1…",
235                         ExpandedUrl = "https://twitter.com/hoge/status/1234567890",
236                     },
237                 },
238             };
239             var quotedStatus = (TwitterStatus?)null;
240
241             var expectedText = "twitter.com/hoge/status/1…";
242
243             Assert.Equal(expectedText, Twitter.CreateAccessibleText(text, entities, quotedStatus, quotedStatusLink: null));
244         }
245
246         [Fact]
247         public void CreateHtmlAnchor_Test()
248         {
249             var text = "@twitterapi #BreakingMyTwitter https://t.co/mIJcSoVSK3";
250             var entities = new TwitterEntities
251             {
252                 UserMentions = new[]
253                 {
254                     new TwitterEntityMention { Indices = new[] { 0, 11 }, ScreenName = "twitterapi" },
255                 },
256                 Hashtags = new[]
257                 {
258                     new TwitterEntityHashtag { Indices = new[] { 12, 30 }, Text = "BreakingMyTwitter" },
259                 },
260                 Urls = new[]
261                 {
262                     new TwitterEntityUrl
263                     {
264                         Indices = new[] { 31, 54 },
265                         Url ="https://t.co/mIJcSoVSK3",
266                         DisplayUrl = "apps-of-a-feather.com",
267                         ExpandedUrl = "http://apps-of-a-feather.com/",
268                     },
269                 },
270             };
271
272             var expectedHtml = @"<a class=""mention"" href=""https://twitter.com/twitterapi"">@twitterapi</a>"
273                 + @" <a class=""hashtag"" href=""https://twitter.com/search?q=%23BreakingMyTwitter"">#BreakingMyTwitter</a>"
274                 + @" <a href=""https://t.co/mIJcSoVSK3"" title=""https://t.co/mIJcSoVSK3"">apps-of-a-feather.com</a>";
275
276             Assert.Equal(expectedHtml, Twitter.CreateHtmlAnchor(text, entities, quotedStatusLink: null));
277         }
278
279         [Fact]
280         public void CreateHtmlAnchor_NicovideoTest()
281         {
282             var text = "sm9";
283             var entities = new TwitterEntities();
284
285             var expectedHtml = @"<a href=""https://www.nicovideo.jp/watch/sm9"">sm9</a>";
286
287             Assert.Equal(expectedHtml, Twitter.CreateHtmlAnchor(text, entities, quotedStatusLink: null));
288         }
289
290         [Fact]
291         public void CreateHtmlAnchor_QuotedUrlWithPermelinkTest()
292         {
293             var text = "hoge";
294             var entities = new TwitterEntities();
295             var quotedStatusLink = new TwitterQuotedStatusPermalink
296             {
297                 Url = "https://t.co/hoge",
298                 Display = "twitter.com/hoge/status/1…",
299                 Expanded = "https://twitter.com/hoge/status/1234567890",
300             };
301
302             var expectedHtml = @"hoge"
303                 + @" <a href=""https://t.co/hoge"" title=""https://t.co/hoge"">twitter.com/hoge/status/1…</a>";
304
305             Assert.Equal(expectedHtml, Twitter.CreateHtmlAnchor(text, entities, quotedStatusLink));
306         }
307
308         [Fact]
309         public void ParseSource_Test()
310         {
311             var sourceHtml = "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>";
312
313             var expected = ("Twitter Web Client", new Uri("http://twitter.com/"));
314             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
315         }
316
317         [Fact]
318         public void ParseSource_PlainTextTest()
319         {
320             var sourceHtml = "web";
321
322             var expected = ("web", (Uri?)null);
323             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
324         }
325
326         [Fact]
327         public void ParseSource_RelativeUriTest()
328         {
329             // 参照: https://twitter.com/kim_upsilon/status/477796052049752064
330             var sourceHtml = "<a href=\"erased_45416\" rel=\"nofollow\">erased_45416</a>";
331
332             var expected = ("erased_45416", new Uri("https://twitter.com/erased_45416"));
333             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
334         }
335
336         [Fact]
337         public void ParseSource_EmptyTest()
338         {
339             // 参照: https://twitter.com/kim_upsilon/status/595156014032244738
340             var sourceHtml = "";
341
342             var expected = ("", (Uri?)null);
343             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
344         }
345
346         [Fact]
347         public void ParseSource_NullTest()
348         {
349             string? sourceHtml = null;
350
351             var expected = ("", (Uri?)null);
352             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
353         }
354
355         [Fact]
356         public void ParseSource_UnescapeTest()
357         {
358             var sourceHtml = "<a href=\"http://example.com/?aaa=123&amp;bbb=456\" rel=\"nofollow\">&lt;&lt;hogehoge&gt;&gt;</a>";
359
360             var expected = ("<<hogehoge>>", new Uri("http://example.com/?aaa=123&bbb=456"));
361             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
362         }
363
364         [Fact]
365         public void ParseSource_UnescapeNoUriTest()
366         {
367             var sourceHtml = "&lt;&lt;hogehoge&gt;&gt;";
368
369             var expected = ("<<hogehoge>>", (Uri?)null);
370             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
371         }
372
373         [Fact]
374         public void GetQuoteTweetStatusIds_EntityTest()
375         {
376             var entities = new[]
377             {
378                 new TwitterEntityUrl
379                 {
380                     Url = "https://t.co/3HXq0LrbJb",
381                     ExpandedUrl = "https://twitter.com/kim_upsilon/status/599261132361072640",
382                 },
383             };
384
385             var statusIds = Twitter.GetQuoteTweetStatusIds(entities, quotedStatusLink: null);
386             Assert.Equal(new[] { 599261132361072640L }, statusIds);
387         }
388
389         [Fact]
390         public void GetQuoteTweetStatusIds_QuotedStatusLinkTest()
391         {
392             var entities = new TwitterEntities();
393             var quotedStatusLink = new TwitterQuotedStatusPermalink
394             {
395                 Url = "https://t.co/3HXq0LrbJb",
396                 Expanded = "https://twitter.com/kim_upsilon/status/599261132361072640",
397             };
398
399             var statusIds = Twitter.GetQuoteTweetStatusIds(entities, quotedStatusLink);
400             Assert.Equal(new[] { 599261132361072640L }, statusIds);
401         }
402
403         [Fact]
404         public void GetQuoteTweetStatusIds_UrlStringTest()
405         {
406             var urls = new[]
407             {
408                 "https://twitter.com/kim_upsilon/status/599261132361072640",
409             };
410
411             var statusIds = Twitter.GetQuoteTweetStatusIds(urls);
412             Assert.Equal(new[] { 599261132361072640L }, statusIds);
413         }
414
415         [Fact]
416         public void GetQuoteTweetStatusIds_OverflowTest()
417         {
418             var urls = new[]
419             {
420                 // 符号付き 64 ビット整数の範囲を超える値
421                 "https://twitter.com/kim_upsilon/status/9999999999999999999",
422             };
423
424             var statusIds = Twitter.GetQuoteTweetStatusIds(urls);
425             Assert.Empty(statusIds);
426         }
427
428         [Fact]
429         public void GetApiResultCount_DefaultTest()
430         {
431             var oldInstance = SettingManagerTest.Common;
432             SettingManagerTest.Common = new SettingCommon();
433
434             var timeline = SettingManager.Common.CountApi;
435             var reply = SettingManager.Common.CountApiReply;
436             var more = SettingManager.Common.MoreCountApi;
437             var startup = SettingManager.Common.FirstCountApi;
438             var favorite = SettingManager.Common.FavoritesCountApi;
439             var list = SettingManager.Common.ListCountApi;
440             var search = SettingManager.Common.SearchCountApi;
441             var usertl = SettingManager.Common.UserTimelineCountApi;
442
443             // デフォルト値チェック
444             Assert.False(SettingManager.Common.UseAdditionalCount);
445             Assert.Equal(60, timeline);
446             Assert.Equal(40, reply);
447             Assert.Equal(200, more);
448             Assert.Equal(100, startup);
449             Assert.Equal(40, favorite);
450             Assert.Equal(100, list);
451             Assert.Equal(100, search);
452             Assert.Equal(20, usertl);
453
454             // Timeline,Reply
455             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
456             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
457
458             // その他はTimelineと同値になる
459             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
460             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
461             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
462             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
463
464             SettingManagerTest.Common = oldInstance;
465         }
466
467         [Fact]
468         public void GetApiResultCount_AdditionalCountTest()
469         {
470             var oldInstance = SettingManagerTest.Common;
471             SettingManagerTest.Common = new SettingCommon();
472
473             var timeline = SettingManager.Common.CountApi;
474             var reply = SettingManager.Common.CountApiReply;
475             var more = SettingManager.Common.MoreCountApi;
476             var startup = SettingManager.Common.FirstCountApi;
477             var favorite = SettingManager.Common.FavoritesCountApi;
478             var list = SettingManager.Common.ListCountApi;
479             var search = SettingManager.Common.SearchCountApi;
480             var usertl = SettingManager.Common.UserTimelineCountApi;
481
482             SettingManager.Common.UseAdditionalCount = true;
483
484             // Timeline
485             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
486             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, true, false));
487             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, true));
488
489             // Reply
490             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
491             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, true, false));
492             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, true));  //Replyの値が使われる
493
494             // Favorites
495             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
496             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
497             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
498
499             SettingManager.Common.FavoritesCountApi = 0;
500
501             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
502             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
503             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
504
505             // List
506             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
507             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
508             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
509
510             SettingManager.Common.ListCountApi = 0;
511
512             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
513             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
514             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
515
516             // PublicSearch
517             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
518             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));
519             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
520
521             SettingManager.Common.SearchCountApi = 0;
522
523             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
524             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));  //MoreCountApiの値がPublicSearchの最大値に制限される
525             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
526
527             // UserTimeline
528             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
529             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
530             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
531
532             SettingManager.Common.UserTimelineCountApi = 0;
533
534             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
535             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
536             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
537
538             SettingManagerTest.Common = oldInstance;
539         }
540
541         [Fact]
542         public void GetTextLengthRemain_Test()
543         {
544             using (var twitter = new Twitter())
545             {
546                 Assert.Equal(280, twitter.GetTextLengthRemain(""));
547                 Assert.Equal(272, twitter.GetTextLengthRemain("hogehoge"));
548             }
549         }
550
551         [Fact]
552         public void GetTextLengthRemain_DirectMessageTest()
553         {
554             using (var twitter = new Twitter())
555             {
556                 // 2015年8月から DM の文字数上限が 10,000 文字に変更された
557                 // https://twittercommunity.com/t/41348
558                 twitter.Configuration.DmTextCharacterLimit = 10000;
559
560                 Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
561                 Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
562
563                 // t.co に短縮される分の文字数を考慮
564                 twitter.Configuration.ShortUrlLength = 20;
565                 Assert.Equal(9971, twitter.GetTextLengthRemain("D twitter hogehoge http://example.com/"));
566
567                 twitter.Configuration.ShortUrlLengthHttps = 21;
568                 Assert.Equal(9970, twitter.GetTextLengthRemain("D twitter hogehoge https://example.com/"));
569             }
570         }
571
572         [Fact]
573         public void GetTextLengthRemain_UrlTest()
574         {
575             using (var twitter = new Twitter())
576             {
577                 // t.co に短縮される分の文字数を考慮
578                 twitter.TextConfiguration.TransformedURLLength = 20;
579                 Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/"));
580                 Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
581                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
582
583                 Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/"));
584                 Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
585                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
586             }
587         }
588
589         [Fact]
590         public void GetTextLengthRemain_UrlWithoutSchemeTest()
591         {
592             using (var twitter = new Twitter())
593             {
594                 // t.co に短縮される分の文字数を考慮
595                 twitter.TextConfiguration.TransformedURLLength = 20;
596                 Assert.Equal(260, twitter.GetTextLengthRemain("example.com"));
597                 Assert.Equal(260, twitter.GetTextLengthRemain("example.com/hogehoge"));
598                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge example.com"));
599
600                 // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
601                 Assert.Equal(270, twitter.GetTextLengthRemain("example.jp"));
602                 // ただし、末尾にパスが続く場合は t.co に短縮される
603                 Assert.Equal(260, twitter.GetTextLengthRemain("example.jp/hogehoge"));
604             }
605         }
606
607         [Fact]
608         public void GetTextLengthRemain_SurrogatePairTest()
609         {
610             using (var twitter = new Twitter())
611             {
612                 Assert.Equal(278, twitter.GetTextLengthRemain("🍣"));
613                 Assert.Equal(267, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
614             }
615         }
616
617         [Fact]
618         public void GetTextLengthRemain_EmojiTest()
619         {
620             using (var twitter = new Twitter())
621             {
622                 // 絵文字の文字数カウントの仕様変更に対するテストケース
623                 // https://twittercommunity.com/t/114607
624
625                 Assert.Equal(279, twitter.GetTextLengthRemain("©")); // 基本多言語面の絵文字
626                 Assert.Equal(277, twitter.GetTextLengthRemain("©\uFE0E")); // 異字体セレクタ付き (text style)
627                 Assert.Equal(279, twitter.GetTextLengthRemain("©\uFE0F")); // 異字体セレクタ付き (emoji style)
628                 Assert.Equal(278, twitter.GetTextLengthRemain("🍣")); // 拡張面の絵文字
629                 Assert.Equal(279, twitter.GetTextLengthRemain("#⃣")); // 合字で表現される絵文字
630                 Assert.Equal(278, twitter.GetTextLengthRemain("👦\U0001F3FF")); // Emoji modifier 付きの絵文字
631                 Assert.Equal(278, twitter.GetTextLengthRemain("\U0001F3FF")); // Emoji modifier 単体
632                 Assert.Equal(278, twitter.GetTextLengthRemain("👨\u200D🎨")); // ZWJ で結合された絵文字
633                 Assert.Equal(278, twitter.GetTextLengthRemain("🏃\u200D♀\uFE0F")); // ZWJ と異字体セレクタを含む絵文字
634             }
635         }
636
637         [Fact]
638         public void GetTextLengthRemain_BrokenSurrogateTest()
639         {
640             using (var twitter = new Twitter())
641             {
642                 // 投稿欄に IME から絵文字を入力すると HighSurrogate のみ入力された状態で TextChanged イベントが呼ばれることがある
643                 Assert.Equal(278, twitter.GetTextLengthRemain("\ud83d"));
644                 Assert.Equal(9999, twitter.GetTextLengthRemain("D twitter \ud83d"));
645             }
646         }
647     }
648 }