OSDN Git Service

起動時にタブ設定を読み込むタイミングを変更
[opentween/open-tween.git] / OpenTween.Tests / TweenMainTest.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.IO;
25 using System.Linq;
26 using System.Reflection;
27 using System.Text;
28 using System.Text.RegularExpressions;
29 using System.Windows.Forms;
30 using OpenTween.Api.DataModel;
31 using OpenTween.Models;
32 using OpenTween.OpenTweenCustomControl;
33 using OpenTween.Setting;
34 using OpenTween.SocialProtocol;
35 using OpenTween.Thumbnail;
36 using Xunit;
37 using Xunit.Extensions;
38
39 namespace OpenTween
40 {
41     public class TweenMainTest
42     {
43         private record TestContext(
44             SettingManager Settings,
45             TabInformations TabInfo
46         );
47
48         private void UsingTweenMain(Action<TweenMain, TestContext> func)
49         {
50             var settings = new SettingManager("");
51             var tabinfo = new TabInformations();
52             using var accounts = new AccountCollection();
53             using var imageCache = new ImageCache();
54             using var iconAssets = new IconAssetsManager();
55             var thumbnailGenerator = new ThumbnailGenerator(new(autoupdate: false));
56
57             // TabInformation.GetInstance() で取得できるようにする
58             var field = typeof(TabInformations).GetField("Instance",
59                 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField);
60             field.SetValue(null, tabinfo);
61
62             tabinfo.AddDefaultTabs();
63
64             using var tweenMain = new TweenMain(settings, tabinfo, accounts, imageCache, iconAssets, thumbnailGenerator);
65             var context = new TestContext(settings, tabinfo);
66
67             func(tweenMain, context);
68         }
69
70         [WinFormsFact]
71         public void Initialize_Test()
72             => this.UsingTweenMain((_, _) => { });
73
74         [WinFormsFact]
75         public void AddNewTab_FilterTabTest()
76         {
77             this.UsingTweenMain((tweenMain, context) =>
78             {
79                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
80
81                 var tab = new FilterTabModel("hoge");
82                 context.TabInfo.AddTab(tab);
83                 tweenMain.AddNewTab(tab, startup: false);
84
85                 Assert.Equal(5, tweenMain.ListTab.TabPages.Count);
86
87                 var tabPage = tweenMain.ListTab.TabPages[4];
88                 Assert.Equal("hoge", tabPage.Text);
89                 Assert.Single(tabPage.Controls);
90                 Assert.IsType<DetailsListView>(tabPage.Controls[0]);
91             });
92         }
93
94         [WinFormsFact]
95         public void AddNewTab_UserTimelineTabTest()
96         {
97             this.UsingTweenMain((tweenMain, context) =>
98             {
99                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
100
101                 var tab = new UserTimelineTabModel("hoge", "twitterapi");
102                 context.TabInfo.AddTab(tab);
103                 tweenMain.AddNewTab(tab, startup: false);
104
105                 Assert.Equal(5, tweenMain.ListTab.TabPages.Count);
106
107                 var tabPage = tweenMain.ListTab.TabPages[4];
108                 Assert.Equal("hoge", tabPage.Text);
109                 Assert.Equal(2, tabPage.Controls.Count);
110                 Assert.IsType<DetailsListView>(tabPage.Controls[0]);
111
112                 var label = Assert.IsType<Label>(tabPage.Controls[1]);
113                 Assert.Equal("twitterapi's Timeline", label.Text);
114             });
115         }
116
117         [WinFormsFact]
118         public void AddNewTab_ListTimelineTabTest()
119         {
120             this.UsingTweenMain((tweenMain, context) =>
121             {
122                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
123
124                 var list = new ListElement
125                 {
126                     Id = 12345L,
127                     Name = "tetete",
128                     Username = "opentween",
129                     IsPublic = false,
130                 };
131                 var tab = new ListTimelineTabModel("hoge", list);
132                 context.TabInfo.AddTab(tab);
133                 tweenMain.AddNewTab(tab, startup: false);
134
135                 Assert.Equal(5, tweenMain.ListTab.TabPages.Count);
136
137                 var tabPage = tweenMain.ListTab.TabPages[4];
138                 Assert.Equal("hoge", tabPage.Text);
139                 Assert.Equal(2, tabPage.Controls.Count);
140                 Assert.IsType<DetailsListView>(tabPage.Controls[0]);
141
142                 var label = Assert.IsType<Label>(tabPage.Controls[1]);
143                 Assert.Equal("@opentween/tetete [Protected]", label.Text);
144             });
145         }
146
147         [WinFormsFact]
148         public void AddNewTab_PublicSearchTabTest()
149         {
150             this.UsingTweenMain((tweenMain, context) =>
151             {
152                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
153
154                 var tab = new PublicSearchTabModel("hoge")
155                 {
156                     SearchWords = "#OpenTween",
157                     SearchLang = "ja",
158                 };
159                 context.TabInfo.AddTab(tab);
160                 tweenMain.AddNewTab(tab, startup: false);
161
162                 Assert.Equal(5, tweenMain.ListTab.TabPages.Count);
163
164                 var tabPage = tweenMain.ListTab.TabPages[4];
165                 Assert.Equal("hoge", tabPage.Text);
166                 Assert.Equal(2, tabPage.Controls.Count);
167                 Assert.IsType<DetailsListView>(tabPage.Controls[0]);
168
169                 var panel = Assert.IsType<Panel>(tabPage.Controls[1]);
170                 Assert.Equal(4, panel.Controls.Count);
171
172                 var comboSearchWord = Assert.IsType<ComboBox>(panel.Controls[0]);
173                 Assert.Equal("#OpenTween", comboSearchWord.Text);
174
175                 var comboSearchLang = Assert.IsType<ComboBox>(panel.Controls[1]);
176                 Assert.Equal("ja", comboSearchLang.Text);
177
178                 Assert.IsType<Button>(panel.Controls[2]);
179                 Assert.IsType<Label>(panel.Controls[3]);
180             });
181         }
182
183         [WinFormsFact]
184         public void RemoveSpecifiedTab_Test()
185         {
186             this.UsingTweenMain((tweenMain, context) =>
187             {
188                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
189
190                 var tab = new PublicSearchTabModel("hoge")
191                 {
192                     SearchWords = "#OpenTween",
193                     SearchLang = "ja",
194                 };
195                 context.TabInfo.AddTab(tab);
196                 tweenMain.AddNewTab(tab, startup: false);
197                 Assert.Equal(5, tweenMain.ListTab.TabPages.Count);
198
199                 var tabPage = tweenMain.ListTab.TabPages[4];
200                 var listView = (DetailsListView)tabPage.Controls[0];
201                 var searchPanel = (Panel)tabPage.Controls[1];
202                 Assert.Equal("hoge", tabPage.Text);
203
204                 tweenMain.RemoveSpecifiedTab("hoge", confirm: false);
205
206                 Assert.Equal(4, tweenMain.ListTab.TabPages.Count);
207                 Assert.False(context.TabInfo.ContainsTab("hoge"));
208                 Assert.True(tabPage.IsDisposed);
209                 Assert.True(listView.IsDisposed);
210                 Assert.True(searchPanel.IsDisposed);
211             });
212         }
213
214         [WinFormsFact]
215         public void RefreshTimeline_Test()
216         {
217             this.UsingTweenMain((tweenMain, context) =>
218             {
219                 var tabPage = tweenMain.ListTab.TabPages[0];
220                 Assert.Equal("Recent", tabPage.Text);
221
222                 var listView = (DetailsListView)tabPage.Controls[0];
223                 Assert.Equal(0, listView.VirtualListSize);
224
225                 var post = new PostClass
226                 {
227                     StatusId = new TwitterStatusId("100"),
228                     Text = "hoge",
229                     UserId = 111L,
230                     ScreenName = "opentween",
231                     CreatedAt = new(2024, 1, 1, 0, 0, 0),
232                 };
233                 context.TabInfo.AddPost(post);
234                 context.TabInfo.DistributePosts();
235                 tweenMain.RefreshTimeline();
236
237                 Assert.Equal(1, listView.VirtualListSize);
238
239                 var listItem = listView.Items[0];
240                 Assert.Equal("opentween", listItem.SubItems[4].Text);
241             });
242         }
243
244         [WinFormsFact]
245         public void FormatStatusText_NewLineTest()
246         {
247             this.UsingTweenMain((tweenMain, _) =>
248             {
249                 Assert.Equal("aaa\nbbb", tweenMain.FormatStatusText("aaa\r\nbbb"));
250             });
251         }
252
253         [WinFormsFact]
254         public void FormatStatusText_NewLineInDMTest()
255         {
256             this.UsingTweenMain((tweenMain, _) =>
257             {
258                 // DM にも適用する
259                 Assert.Equal("D opentween aaa\nbbb", tweenMain.FormatStatusText("D opentween aaa\r\nbbb"));
260             });
261         }
262
263         [WinFormsFact]
264         public void FormatStatusText_SeparateUrlAndFullwidthCharacter_EnabledTest()
265         {
266             this.UsingTweenMain((tweenMain, context) =>
267             {
268                 tweenMain.SeparateUrlAndFullwidthCharacter = true;
269                 Assert.Equal("https://example.com/ あああ", tweenMain.FormatStatusText("https://example.com/あああ"));
270             });
271         }
272
273         [WinFormsFact]
274         public void FormatStatusText_SeparateUrlAndFullwidthCharacter_DisabledTest()
275         {
276             this.UsingTweenMain((tweenMain, context) =>
277             {
278                 tweenMain.SeparateUrlAndFullwidthCharacter = false;
279                 Assert.Equal("https://example.com/あああ", tweenMain.FormatStatusText("https://example.com/あああ"));
280             });
281         }
282
283         [WinFormsFact]
284         public void FormatStatusText_ReplaceFullwidthSpace_EnabledTest()
285         {
286             this.UsingTweenMain((tweenMain, context) =>
287             {
288                 context.Settings.Common.WideSpaceConvert = true;
289                 Assert.Equal("aaa bbb", tweenMain.FormatStatusText("aaa bbb"));
290             });
291         }
292
293         [WinFormsFact]
294         public void FormatStatusText_ReplaceFullwidthSpaceInDM_EnabledTest()
295         {
296             this.UsingTweenMain((tweenMain, context) =>
297             {
298                 context.Settings.Common.WideSpaceConvert = true;
299
300                 // DM にも適用する
301                 Assert.Equal("D opentween aaa bbb", tweenMain.FormatStatusText("D opentween aaa bbb"));
302             });
303         }
304
305         [WinFormsFact]
306         public void FormatStatusText_ReplaceFullwidthSpace_DisabledTest()
307         {
308             this.UsingTweenMain((tweenMain, context) =>
309             {
310                 context.Settings.Common.WideSpaceConvert = false;
311                 Assert.Equal("aaa bbb", tweenMain.FormatStatusText("aaa bbb"));
312             });
313         }
314
315         [WinFormsFact]
316         public void FormatStatusText_UseRecommendedFooter_Test()
317         {
318             this.UsingTweenMain((tweenMain, context) =>
319             {
320                 context.Settings.Local.UseRecommendStatus = true;
321                 Assert.Matches(new Regex(@"^aaa \[TWNv\d+\]$"), tweenMain.FormatStatusText("aaa"));
322             });
323         }
324
325         [WinFormsFact]
326         public void FormatStatusText_CustomFooterText_Test()
327         {
328             this.UsingTweenMain((tweenMain, context) =>
329             {
330                 context.Settings.Local.StatusText = "foo";
331                 Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa"));
332             });
333         }
334
335         [WinFormsFact]
336         public void FormatStatusText_DisableFooterIfSendingDM_Test()
337         {
338             this.UsingTweenMain((tweenMain, context) =>
339             {
340                 context.Settings.Local.StatusText = "foo";
341
342                 // DM の場合はフッターを無効化する
343                 Assert.Equal("D opentween aaa", tweenMain.FormatStatusText("D opentween aaa"));
344             });
345         }
346
347         [WinFormsFact]
348         public void FormatStatusText_DisableFooterIfContainsUnofficialRT_Test()
349         {
350             this.UsingTweenMain((tweenMain, context) =>
351             {
352                 context.Settings.Local.StatusText = "foo";
353
354                 // 非公式 RT を含む場合はフッターを無効化する
355                 Assert.Equal("aaa RT @foo: bbb", tweenMain.FormatStatusText("aaa RT @foo: bbb"));
356             });
357         }
358
359         [WinFormsFact]
360         public void FormatStatusText_DisableFooterIfPostByEnterAndPressedShiftKey_Test()
361         {
362             this.UsingTweenMain((tweenMain, context) =>
363             {
364                 context.Settings.Common.PostCtrlEnter = false;
365                 context.Settings.Common.PostShiftEnter = false; // Enter で投稿する設定
366                 context.Settings.Local.StatusText = "foo";
367                 context.Settings.Local.StatusMultiline = false; // 単一行モード
368
369                 // Shift キーが押されている場合はフッターを無効化する
370                 Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift));
371             });
372         }
373
374         [WinFormsFact]
375         public void FormatStatusText_DisableFooterIfPostByEnterAndPressedCtrlKeyAndMultilineMode_Test()
376         {
377             this.UsingTweenMain((tweenMain, context) =>
378             {
379                 context.Settings.Common.PostCtrlEnter = false;
380                 context.Settings.Common.PostShiftEnter = false; // Enter で投稿する設定
381                 context.Settings.Local.StatusText = "foo";
382                 context.Settings.Local.StatusMultiline = true; // 複数行モード
383
384                 // Ctrl キーが押されている場合はフッターを無効化する
385                 Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control));
386             });
387         }
388
389         [WinFormsFact]
390         public void FormatStatusText_DisableFooterIfPostByShiftEnterAndPressedControlKey_Test()
391         {
392             this.UsingTweenMain((tweenMain, context) =>
393             {
394                 context.Settings.Common.PostCtrlEnter = false;
395                 context.Settings.Common.PostShiftEnter = true; // Shift+Enter で投稿する設定
396                 context.Settings.Local.StatusText = "foo";
397
398                 // Ctrl キーが押されている場合はフッターを無効化する
399                 Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control));
400             });
401         }
402
403         [WinFormsFact]
404         public void FormatStatusText_EnableFooterIfPostByShiftEnter_Test()
405         {
406             this.UsingTweenMain((tweenMain, context) =>
407             {
408                 context.Settings.Common.PostCtrlEnter = false;
409                 context.Settings.Common.PostShiftEnter = true; // Shift+Enter で投稿する設定
410                 context.Settings.Local.StatusText = "foo";
411
412                 // Shift+Enter で投稿する場合、Ctrl キーが押されていなければフッターを付ける
413                 Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift));
414             });
415         }
416
417         [WinFormsFact]
418         public void FormatStatusText_DisableFooterIfPostByCtrlEnterAndPressedShiftKey_Test()
419         {
420             this.UsingTweenMain((tweenMain, context) =>
421             {
422                 context.Settings.Common.PostCtrlEnter = true; // Ctrl+Enter で投稿する設定
423                 context.Settings.Common.PostShiftEnter = false;
424                 context.Settings.Local.StatusText = "foo";
425
426                 // Shift キーが押されている場合はフッターを無効化する
427                 Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift));
428             });
429         }
430
431         [WinFormsFact]
432         public void FormatStatusText_EnableFooterIfPostByCtrlEnter_Test()
433         {
434             this.UsingTweenMain((tweenMain, context) =>
435             {
436                 context.Settings.Common.PostCtrlEnter = true; // Ctrl+Enter で投稿する設定
437                 context.Settings.Common.PostShiftEnter = false;
438                 context.Settings.Local.StatusText = "foo";
439
440                 // Ctrl+Enter で投稿する場合、Shift キーが押されていなければフッターを付ける
441                 Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control));
442             });
443         }
444
445         [WinFormsFact]
446         public void FormatStatusText_PreventSmsCommand_Test()
447         {
448             this.UsingTweenMain((tweenMain, context) =>
449             {
450                 // 「D+」などから始まる文字列をツイートしようとすると SMS コマンドと誤認されてエラーが返される問題の回避策
451                 Assert.Equal("\u200bd+aaaa", tweenMain.FormatStatusText("d+aaaa"));
452             });
453         }
454
455         [Fact]
456         public void GetUrlFromDataObject_XMozUrlTest()
457         {
458             var dataBytes = Encoding.Unicode.GetBytes("https://twitter.com/\nTwitter\0");
459             using var memstream = new MemoryStream(dataBytes);
460             var data = new DataObject("text/x-moz-url", memstream);
461
462             var expected = ("https://twitter.com/", "Twitter");
463             Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
464         }
465
466         [Fact]
467         public void GetUrlFromDataObject_IESiteModeToUrlTest()
468         {
469             var dataBytes = Encoding.Unicode.GetBytes("https://twitter.com/\0Twitter\0");
470             using var memstream = new MemoryStream(dataBytes);
471             var data = new DataObject("IESiteModeToUrl", memstream);
472
473             var expected = ("https://twitter.com/", "Twitter");
474             Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
475         }
476
477         [Fact]
478         public void GetUrlFromDataObject_UniformResourceLocatorWTest()
479         {
480             var dataBytes = Encoding.Unicode.GetBytes("https://twitter.com/\0");
481             using var memstream = new MemoryStream(dataBytes);
482             var data = new DataObject("UniformResourceLocatorW", memstream);
483
484             var expected = ("https://twitter.com/", (string?)null);
485             Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
486         }
487
488         [Fact]
489         public void GetUrlFromDataObject_UnknownFormatTest()
490         {
491             using var memstream = new MemoryStream(Array.Empty<byte>());
492             var data = new DataObject("application/x-hogehoge", memstream);
493
494             Assert.Throws<NotSupportedException>(() => TweenMain.GetUrlFromDataObject(data));
495         }
496
497         [Fact]
498         public void CreateRetweetUnofficial_UrlTest()
499         {
500             var statusText = """<a href="http://t.co/KYi7vMZzRt" title="http://twitter.com/">twitter.com</a>""";
501
502             Assert.Equal("http://twitter.com/", TweenMain.CreateRetweetUnofficial(statusText, false));
503         }
504
505         [Fact]
506         public void CreateRetweetUnofficial_MentionTest()
507         {
508             var statusText = """<a class="mention" href="https://twitter.com/twitterapi">@TwitterAPI</a>""";
509
510             Assert.Equal("@TwitterAPI", TweenMain.CreateRetweetUnofficial(statusText, false));
511         }
512
513         [Fact]
514         public void CreateRetweetUnofficial_HashtagTest()
515         {
516             var statusText = """<a class="hashtag" href="https://twitter.com/search?q=%23OpenTween">#OpenTween</a>""";
517
518             Assert.Equal("#OpenTween", TweenMain.CreateRetweetUnofficial(statusText, false));
519         }
520
521         [Fact]
522         public void CreateRetweetUnofficial_SingleLineTest()
523         {
524             var statusText = "123<br>456<br>789";
525
526             Assert.Equal("123 456 789", TweenMain.CreateRetweetUnofficial(statusText, false));
527         }
528
529         [Fact]
530         public void CreateRetweetUnofficial_MultiLineTest()
531         {
532             var statusText = "123<br>456<br>789";
533
534             Assert.Equal("123" + Environment.NewLine + "456" + Environment.NewLine + "789", TweenMain.CreateRetweetUnofficial(statusText, true));
535         }
536
537         [Fact]
538         public void CreateRetweetUnofficial_DecodeTest()
539         {
540             var statusText = "&lt;&gt;&quot;&#39;&nbsp;";
541
542             Assert.Equal("<>\"' ", TweenMain.CreateRetweetUnofficial(statusText, false));
543         }
544
545         [Fact]
546         public void CreateRetweetUnofficial_WithFormatterTest()
547         {
548             // TweetFormatterでHTMLに整形 → CreateRetweetUnofficialで復元 までの動作が正しく行えているか
549
550             var text = "#てすと @TwitterAPI \n http://t.co/KYi7vMZzRt";
551             var entities = new TwitterEntity[]
552             {
553                 new TwitterEntityHashtag
554                 {
555                     Indices = new[] { 0, 4 },
556                     Text = "てすと",
557                 },
558                 new TwitterEntityMention
559                 {
560                     Indices = new[] { 5, 16 },
561                     Id = 6253282L,
562                     Name = "Twitter API",
563                     ScreenName = "twitterapi",
564                 },
565                 new TwitterEntityUrl
566                 {
567                     Indices = new[] { 19, 41 },
568                     DisplayUrl = "twitter.com",
569                     ExpandedUrl = "http://twitter.com/",
570                     Url = "http://t.co/KYi7vMZzRt",
571                 },
572             };
573
574             var html = TweetFormatter.AutoLinkHtml(text, entities);
575
576             var expected = "#てすと @TwitterAPI " + Environment.NewLine + " http://twitter.com/";
577             Assert.Equal(expected, TweenMain.CreateRetweetUnofficial(html, true));
578         }
579
580         [Theory]
581         [InlineData("", true)]
582         [InlineData("hoge", false)]
583         [InlineData("@twitterapi ", true)]
584         [InlineData("@twitterapi @opentween ", true)]
585         [InlineData("@twitterapi @opentween hoge", false)]
586         public void TextContainsOnlyMentions_Test(string input, bool expected)
587             => Assert.Equal(expected, TweenMain.TextContainsOnlyMentions(input));
588     }
589 }