OSDN Git Service

式形式のメソッドを使用する (IDE0021, IDE0022, IDE0025, IDE0053)
[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 quotedStatus = new TwitterStatus
296             {
297                 Id = 1234567890L,
298                 IdStr = "1234567890",
299                 User = new TwitterUser
300                 {
301                     Id = 1111,
302                     IdStr = "1111",
303                     ScreenName = "foo",
304                 },
305                 FullText = "test",
306             };
307             var quotedStatusLink = new TwitterQuotedStatusPermalink
308             {
309                 Url = "https://t.co/hoge",
310                 Display = "twitter.com/hoge/status/1…",
311                 Expanded = "https://twitter.com/hoge/status/1234567890",
312             };
313
314             var expectedHtml = @"hoge"
315                 + @" <a href=""https://t.co/hoge"" title=""https://t.co/hoge"">twitter.com/hoge/status/1…</a>";
316
317             Assert.Equal(expectedHtml, Twitter.CreateHtmlAnchor(text, entities, quotedStatusLink));
318         }
319
320         [Fact]
321         public void ParseSource_Test()
322         {
323             var sourceHtml = "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>";
324
325             var expected = ("Twitter Web Client", new Uri("http://twitter.com/"));
326             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
327         }
328
329         [Fact]
330         public void ParseSource_PlainTextTest()
331         {
332             var sourceHtml = "web";
333
334             var expected = ("web", (Uri)null);
335             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
336         }
337
338         [Fact]
339         public void ParseSource_RelativeUriTest()
340         {
341             // 参照: https://twitter.com/kim_upsilon/status/477796052049752064
342             var sourceHtml = "<a href=\"erased_45416\" rel=\"nofollow\">erased_45416</a>";
343
344             var expected = ("erased_45416", new Uri("https://twitter.com/erased_45416"));
345             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
346         }
347
348         [Fact]
349         public void ParseSource_EmptyTest()
350         {
351             // 参照: https://twitter.com/kim_upsilon/status/595156014032244738
352             var sourceHtml = "";
353
354             var expected = ("", (Uri)null);
355             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
356         }
357
358         [Fact]
359         public void ParseSource_NullTest()
360         {
361             string sourceHtml = null;
362
363             var expected = ("", (Uri)null);
364             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
365         }
366
367         [Fact]
368         public void ParseSource_UnescapeTest()
369         {
370             var sourceHtml = "<a href=\"http://example.com/?aaa=123&amp;bbb=456\" rel=\"nofollow\">&lt;&lt;hogehoge&gt;&gt;</a>";
371
372             var expected = ("<<hogehoge>>", new Uri("http://example.com/?aaa=123&bbb=456"));
373             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
374         }
375
376         [Fact]
377         public void ParseSource_UnescapeNoUriTest()
378         {
379             var sourceHtml = "&lt;&lt;hogehoge&gt;&gt;";
380
381             var expected = ("<<hogehoge>>", (Uri)null);
382             Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
383         }
384
385         [Fact]
386         public void GetQuoteTweetStatusIds_EntityTest()
387         {
388             var entities = new[]
389             {
390                 new TwitterEntityUrl
391                 {
392                     Url = "https://t.co/3HXq0LrbJb",
393                     ExpandedUrl = "https://twitter.com/kim_upsilon/status/599261132361072640",
394                 },
395             };
396
397             var statusIds = Twitter.GetQuoteTweetStatusIds(entities, quotedStatusLink: null);
398             Assert.Equal(new[] { 599261132361072640L }, statusIds);
399         }
400
401         [Fact]
402         public void GetQuoteTweetStatusIds_QuotedStatusLinkTest()
403         {
404             var entities = new TwitterEntities();
405             var quotedStatusLink = new TwitterQuotedStatusPermalink
406             {
407                 Url = "https://t.co/3HXq0LrbJb",
408                 Expanded = "https://twitter.com/kim_upsilon/status/599261132361072640",
409             };
410
411             var statusIds = Twitter.GetQuoteTweetStatusIds(entities, quotedStatusLink);
412             Assert.Equal(new[] { 599261132361072640L }, statusIds);
413         }
414
415         [Fact]
416         public void GetQuoteTweetStatusIds_UrlStringTest()
417         {
418             var urls = new[]
419             {
420                 "https://twitter.com/kim_upsilon/status/599261132361072640",
421             };
422
423             var statusIds = Twitter.GetQuoteTweetStatusIds(urls);
424             Assert.Equal(new[] { 599261132361072640L }, statusIds);
425         }
426
427         [Fact]
428         public void GetQuoteTweetStatusIds_OverflowTest()
429         {
430             var urls = new[]
431             {
432                 // 符号付き 64 ビット整数の範囲を超える値
433                 "https://twitter.com/kim_upsilon/status/9999999999999999999",
434             };
435
436             var statusIds = Twitter.GetQuoteTweetStatusIds(urls);
437             Assert.Empty(statusIds);
438         }
439
440         [Fact]
441         public void GetApiResultCount_DefaultTest()
442         {
443             var oldInstance = SettingManagerTest.Common;
444             SettingManagerTest.Common = new SettingCommon();
445
446             var timeline = SettingManager.Common.CountApi;
447             var reply = SettingManager.Common.CountApiReply;
448             var more = SettingManager.Common.MoreCountApi;
449             var startup = SettingManager.Common.FirstCountApi;
450             var favorite = SettingManager.Common.FavoritesCountApi;
451             var list = SettingManager.Common.ListCountApi;
452             var search = SettingManager.Common.SearchCountApi;
453             var usertl = SettingManager.Common.UserTimelineCountApi;
454
455             // デフォルト値チェック
456             Assert.False(SettingManager.Common.UseAdditionalCount);
457             Assert.Equal(60, timeline);
458             Assert.Equal(40, reply);
459             Assert.Equal(200, more);
460             Assert.Equal(100, startup);
461             Assert.Equal(40, favorite);
462             Assert.Equal(100, list);
463             Assert.Equal(100, search);
464             Assert.Equal(20, usertl);
465
466             // Timeline,Reply
467             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
468             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
469
470             // その他はTimelineと同値になる
471             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
472             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
473             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
474             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
475
476             SettingManagerTest.Common = oldInstance;
477         }
478
479         [Fact]
480         public void GetApiResultCount_AdditionalCountTest()
481         {
482             var oldInstance = SettingManagerTest.Common;
483             SettingManagerTest.Common = new SettingCommon();
484
485             var timeline = SettingManager.Common.CountApi;
486             var reply = SettingManager.Common.CountApiReply;
487             var more = SettingManager.Common.MoreCountApi;
488             var startup = SettingManager.Common.FirstCountApi;
489             var favorite = SettingManager.Common.FavoritesCountApi;
490             var list = SettingManager.Common.ListCountApi;
491             var search = SettingManager.Common.SearchCountApi;
492             var usertl = SettingManager.Common.UserTimelineCountApi;
493
494             SettingManager.Common.UseAdditionalCount = true;
495
496             // Timeline
497             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
498             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, true, false));
499             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, true));
500
501             // Reply
502             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
503             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, true, false));
504             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, true));  //Replyの値が使われる
505
506             // Favorites
507             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
508             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
509             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
510
511             SettingManager.Common.FavoritesCountApi = 0;
512
513             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
514             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
515             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
516
517             // List
518             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
519             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
520             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
521
522             SettingManager.Common.ListCountApi = 0;
523
524             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
525             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
526             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
527
528             // PublicSearch
529             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
530             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));
531             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
532
533             SettingManager.Common.SearchCountApi = 0;
534
535             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
536             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));  //MoreCountApiの値がPublicSearchの最大値に制限される
537             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
538
539             // UserTimeline
540             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
541             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
542             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
543
544             SettingManager.Common.UserTimelineCountApi = 0;
545
546             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
547             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
548             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
549
550             SettingManagerTest.Common = oldInstance;
551         }
552
553         [Fact]
554         public void GetTextLengthRemain_Test()
555         {
556             using (var twitter = new Twitter())
557             {
558                 Assert.Equal(280, twitter.GetTextLengthRemain(""));
559                 Assert.Equal(272, twitter.GetTextLengthRemain("hogehoge"));
560             }
561         }
562
563         [Fact]
564         public void GetTextLengthRemain_DirectMessageTest()
565         {
566             using (var twitter = new Twitter())
567             {
568                 // 2015年8月から DM の文字数上限が 10,000 文字に変更された
569                 // https://twittercommunity.com/t/41348
570                 twitter.Configuration.DmTextCharacterLimit = 10000;
571
572                 Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
573                 Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
574
575                 // t.co に短縮される分の文字数を考慮
576                 twitter.Configuration.ShortUrlLength = 20;
577                 Assert.Equal(9971, twitter.GetTextLengthRemain("D twitter hogehoge http://example.com/"));
578
579                 twitter.Configuration.ShortUrlLengthHttps = 21;
580                 Assert.Equal(9970, twitter.GetTextLengthRemain("D twitter hogehoge https://example.com/"));
581             }
582         }
583
584         [Fact]
585         public void GetTextLengthRemain_UrlTest()
586         {
587             using (var twitter = new Twitter())
588             {
589                 // t.co に短縮される分の文字数を考慮
590                 twitter.TextConfiguration.TransformedURLLength = 20;
591                 Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/"));
592                 Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
593                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
594
595                 Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/"));
596                 Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
597                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
598             }
599         }
600
601         [Fact]
602         public void GetTextLengthRemain_UrlWithoutSchemeTest()
603         {
604             using (var twitter = new Twitter())
605             {
606                 // t.co に短縮される分の文字数を考慮
607                 twitter.TextConfiguration.TransformedURLLength = 20;
608                 Assert.Equal(260, twitter.GetTextLengthRemain("example.com"));
609                 Assert.Equal(260, twitter.GetTextLengthRemain("example.com/hogehoge"));
610                 Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge example.com"));
611
612                 // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
613                 Assert.Equal(270, twitter.GetTextLengthRemain("example.jp"));
614                 // ただし、末尾にパスが続く場合は t.co に短縮される
615                 Assert.Equal(260, twitter.GetTextLengthRemain("example.jp/hogehoge"));
616             }
617         }
618
619         [Fact]
620         public void GetTextLengthRemain_SurrogatePairTest()
621         {
622             using (var twitter = new Twitter())
623             {
624                 Assert.Equal(278, twitter.GetTextLengthRemain("🍣"));
625                 Assert.Equal(267, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
626             }
627         }
628
629         [Fact]
630         public void GetTextLengthRemain_EmojiTest()
631         {
632             using (var twitter = new Twitter())
633             {
634                 // 絵文字の文字数カウントの仕様変更に対するテストケース
635                 // https://twittercommunity.com/t/114607
636
637                 Assert.Equal(279, twitter.GetTextLengthRemain("©")); // 基本多言語面の絵文字
638                 Assert.Equal(277, twitter.GetTextLengthRemain("©\uFE0E")); // 異字体セレクタ付き (text style)
639                 Assert.Equal(279, twitter.GetTextLengthRemain("©\uFE0F")); // 異字体セレクタ付き (emoji style)
640                 Assert.Equal(278, twitter.GetTextLengthRemain("🍣")); // 拡張面の絵文字
641                 Assert.Equal(279, twitter.GetTextLengthRemain("#⃣")); // 合字で表現される絵文字
642                 Assert.Equal(278, twitter.GetTextLengthRemain("👦\U0001F3FF")); // Emoji modifier 付きの絵文字
643                 Assert.Equal(278, twitter.GetTextLengthRemain("\U0001F3FF")); // Emoji modifier 単体
644                 Assert.Equal(278, twitter.GetTextLengthRemain("👨\u200D🎨")); // ZWJ で結合された絵文字
645                 Assert.Equal(278, twitter.GetTextLengthRemain("🏃\u200D♀\uFE0F")); // ZWJ と異字体セレクタを含む絵文字
646             }
647         }
648
649         [Fact]
650         public void GetTextLengthRemain_BrokenSurrogateTest()
651         {
652             using (var twitter = new Twitter())
653             {
654                 // 投稿欄に IME から絵文字を入力すると HighSurrogate のみ入力された状態で TextChanged イベントが呼ばれることがある
655                 Assert.Equal(278, twitter.GetTextLengthRemain("\ud83d"));
656                 Assert.Equal(9999, twitter.GetTextLengthRemain("D twitter \ud83d"));
657             }
658         }
659     }
660 }