OSDN Git Service

c6300c8d17489a7747d268b56c596ced54f090fe
[opentween/open-tween.git] / OpenTween.Tests / Models / TabInformationTest.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.Reflection;
26 using System.Text;
27 using Xunit;
28 using Xunit.Extensions;
29
30 namespace OpenTween.Models
31 {
32     public class TabInformationTest
33     {
34         private readonly TabInformations tabinfo;
35
36         public TabInformationTest()
37         {
38             this.tabinfo = Activator.CreateInstance(typeof(TabInformations), true) as TabInformations;
39
40             // TabInformation.GetInstance() で取得できるようにする
41             var field = typeof(TabInformations).GetField("_instance",
42                 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField);
43             field.SetValue(null, this.tabinfo);
44
45             // 標準のタブを追加
46             this.tabinfo.AddTab(new HomeTabModel("Recent"));
47             this.tabinfo.AddTab(new MentionsTabModel("Reply"));
48             this.tabinfo.AddTab(new DirectMessagesTabModel("DM"));
49             this.tabinfo.AddTab(new FavoritesTabModel("Favorites"));
50         }
51
52         [Fact]
53         public void AddTab_Test()
54         {
55             var tab = new FilterTabModel("MyTab");
56             var ret = this.tabinfo.AddTab(tab);
57
58             Assert.True(ret);
59             Assert.Same(tab, this.tabinfo.Tabs.Last());
60         }
61
62         [Fact]
63         public void AddTab_DuplicateTest()
64         {
65             var ret = this.tabinfo.AddTab(new FilterTabModel("Recent"));
66
67             Assert.False(ret);
68         }
69
70         [Fact]
71         public void SelectTab_Test()
72         {
73             this.tabinfo.SelectTab("Reply");
74
75             Assert.Equal("Reply", this.tabinfo.SelectedTabName);
76             Assert.IsType<MentionsTabModel>(this.tabinfo.SelectedTab);
77         }
78
79         [Fact]
80         public void SelectTab_NotExistTest()
81             => Assert.Throws<ArgumentException>(() => this.tabinfo.SelectTab("INVALID"));
82
83         [Fact]
84         public void MakeTabName_Test()
85         {
86             var baseTabName = "NewTab";
87             Assert.Equal("NewTab", this.tabinfo.MakeTabName(baseTabName, 5));
88         }
89
90         [Fact]
91         public void MakeTabName_RetryTest()
92         {
93             this.tabinfo.AddTab(new FilterTabModel("NewTab"));
94             this.tabinfo.AddTab(new FilterTabModel("NewTab2"));
95
96             var baseTabName = "NewTab";
97             Assert.Equal("NewTab3", this.tabinfo.MakeTabName(baseTabName, 5));
98         }
99
100         [Fact]
101         public void MakeTabName_RetryErrorTest()
102         {
103             this.tabinfo.AddTab(new FilterTabModel("NewTab"));
104             this.tabinfo.AddTab(new FilterTabModel("NewTab2"));
105             this.tabinfo.AddTab(new FilterTabModel("NewTab3"));
106             this.tabinfo.AddTab(new FilterTabModel("NewTab4"));
107             this.tabinfo.AddTab(new FilterTabModel("NewTab5"));
108
109             var baseTabName = "NewTab";
110             Assert.Throws<TabException>(() => this.tabinfo.MakeTabName(baseTabName, 5));
111         }
112
113         [Fact]
114         public void IsMuted_Test()
115         {
116             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
117
118             var post = new PostClass
119             {
120                 UserId = 12345L,
121                 Text = "hogehoge",
122             };
123             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
124         }
125
126         [Fact]
127         public void IsMuted_NotMutingTest()
128         {
129             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
130
131             var post = new PostClass
132             {
133                 UserId = 11111L,
134                 Text = "hogehoge",
135             };
136             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
137         }
138
139         [Fact]
140         public void IsMuted_RetweetTest()
141         {
142             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
143
144             var post = new PostClass
145             {
146                 UserId = 11111L,
147                 RetweetedByUserId = 12345L,
148                 Text = "hogehoge",
149             };
150             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
151         }
152
153         [Fact]
154         public void IsMuted_RetweetNotMutingTest()
155         {
156             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
157
158             var post = new PostClass
159             {
160                 UserId = 11111L,
161                 RetweetedByUserId = 22222L,
162                 Text = "hogehoge",
163             };
164             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
165         }
166
167         [Fact]
168         public void IsMuted_ReplyTest()
169         {
170             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
171
172             // ミュート対象のユーザーであってもリプライの場合は対象外とする
173             var post = new PostClass
174             {
175                 UserId = 12345L,
176                 Text = "@foo hogehoge",
177                 IsReply = true,
178             };
179             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
180         }
181
182         [Fact]
183         public void IsMuted_NotInHomeTimelineTest()
184         {
185             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
186
187             // Recent以外のタブ(検索など)の場合は対象外とする
188             var post = new PostClass
189             {
190                 UserId = 12345L,
191                 Text = "hogehoge",
192             };
193             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: false));
194         }
195
196         [Fact]
197         public void IsMuted_MuteTabRulesTest()
198         {
199             this.tabinfo.MuteUserIds = new HashSet<long> { };
200
201             var muteTab = new MuteTabModel();
202             muteTab.AddFilter(new PostFilterRule
203             {
204                 FilterName = "foo",
205                 MoveMatches = true,
206             });
207             this.tabinfo.AddTab(muteTab);
208
209             var post = new PostClass
210             {
211                 UserId = 12345L,
212                 ScreenName = "foo",
213                 Text = "hogehoge",
214             };
215             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
216         }
217
218         [Fact]
219         public void IsMuted_MuteTabRules_NotInHomeTimelineTest()
220         {
221             this.tabinfo.MuteUserIds = new HashSet<long> { };
222
223             var muteTab = new MuteTabModel();
224             muteTab.AddFilter(new PostFilterRule
225             {
226                 FilterName = "foo",
227                 MoveMatches = true,
228             });
229             this.tabinfo.AddTab(muteTab);
230
231             // ミュートタブによるミュートはリプライも対象とする
232             var post = new PostClass
233             {
234                 UserId = 12345L,
235                 ScreenName = "foo",
236                 Text = "@hoge hogehoge",
237                 IsReply = true,
238             };
239             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: false));
240         }
241
242         [Fact]
243         public void SetReadAllTab_MarkAsReadTest()
244         {
245             var tab1 = new PublicSearchTabModel("search1");
246             var tab2 = new PublicSearchTabModel("search2");
247
248             this.tabinfo.AddTab(tab1);
249             this.tabinfo.AddTab(tab2);
250
251             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて未読)
252             tab1.UnreadManage = true;
253             tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = false });
254             tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
255             tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
256
257             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて未読)
258             tab2.UnreadManage = true;
259             tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
260             tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
261             tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = false });
262
263             this.tabinfo.DistributePosts();
264             this.tabinfo.SubmitUpdate();
265
266             // この時点での各タブの未読件数
267             Assert.Equal(3, tab1.UnreadCount);
268             Assert.Equal(3, tab2.UnreadCount);
269
270             // ... ここまで長い前置き
271
272             // StatusId: 200 を既読にする (search1, search2 両方に含まれる)
273             this.tabinfo.SetReadAllTab(200L, read: true);
274             Assert.Equal(2, tab1.UnreadCount);
275             Assert.Equal(2, tab2.UnreadCount);
276
277             // StatusId: 100 を既読にする (search1 のみに含まれる)
278             this.tabinfo.SetReadAllTab(100L, read: true);
279             Assert.Equal(1, tab1.UnreadCount);
280             Assert.Equal(2, tab2.UnreadCount);
281         }
282
283         [Fact]
284         public void SetReadAllTab_MarkAsUnreadTest()
285         {
286             var tab1 = new PublicSearchTabModel("search1");
287             var tab2 = new PublicSearchTabModel("search2");
288
289             this.tabinfo.AddTab(tab1);
290             this.tabinfo.AddTab(tab2);
291
292             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて既読)
293             tab1.UnreadManage = true;
294             tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = true });
295             tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
296             tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
297
298             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて既読)
299             tab2.UnreadManage = true;
300             tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
301             tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
302             tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = true });
303
304             this.tabinfo.DistributePosts();
305             this.tabinfo.SubmitUpdate();
306
307             // この時点での各タブの未読件数
308             Assert.Equal(0, tab1.UnreadCount);
309             Assert.Equal(0, tab2.UnreadCount);
310
311             // ... ここまで長い前置き
312
313             // StatusId: 200 を未読にする (search1, search2 両方に含まれる)
314             this.tabinfo.SetReadAllTab(200L, read: false);
315             Assert.Equal(1, tab1.UnreadCount);
316             Assert.Equal(1, tab2.UnreadCount);
317
318             // StatusId: 100 を未読にする (search1 のみに含まれる)
319             this.tabinfo.SetReadAllTab(100L, read: false);
320             Assert.Equal(2, tab1.UnreadCount);
321             Assert.Equal(1, tab2.UnreadCount);
322         }
323
324         [Fact]
325         public void SetReadHomeTab_Test()
326         {
327             var homeTab = this.tabinfo.Tabs["Recent"];
328
329             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
330             homeTab.UnreadManage = true;
331             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
332             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
333             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
334
335             this.tabinfo.DistributePosts();
336             this.tabinfo.SubmitUpdate();
337
338             // この時点でのHomeタブの未読件数
339             Assert.Equal(3, homeTab.UnreadCount);
340
341             // Recent タブのツイートをすべて未読にする
342             this.tabinfo.SetReadHomeTab();
343             Assert.Equal(0, homeTab.UnreadCount);
344         }
345
346         [Fact]
347         public void SetReadHomeTab_ContainsReplyTest()
348         {
349             var homeTab = this.tabinfo.Tabs["Recent"];
350
351             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
352             // StatusId: 150 は未読だがリプライ属性が付いている
353             homeTab.UnreadManage = true;
354             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
355             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false, IsReply = true });
356             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
357
358             this.tabinfo.DistributePosts();
359             this.tabinfo.SubmitUpdate();
360
361             // この時点でのHomeタブの未読件数
362             Assert.Equal(3, homeTab.UnreadCount);
363
364             // Recent タブのツイートをすべて未読にする
365             this.tabinfo.SetReadHomeTab();
366
367             // リプライである StatusId: 150 を除いてすべて未読になっている
368             Assert.Equal(1, homeTab.UnreadCount);
369             Assert.Equal(150L, homeTab.NextUnreadId);
370         }
371
372         [Fact]
373         public void SetReadHomeTab_ContainsFilterHitTest()
374         {
375             var homeTab = this.tabinfo.Tabs["Recent"];
376
377             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
378             homeTab.UnreadManage = true;
379             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
380             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
381             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
382
383             // StatusId: 150 だけ FilterTab の振り分けルールにヒットする (PostClass.FilterHit が true になる)
384             var filterTab = new FilterTabModel("FilterTab");
385             filterTab.AddFilter(TestPostFilterRule.Create(x =>
386                 x.StatusId == 150L ? MyCommon.HITRESULT.Copy : MyCommon.HITRESULT.None));
387             this.tabinfo.AddTab(filterTab);
388
389             this.tabinfo.DistributePosts();
390             this.tabinfo.SubmitUpdate();
391
392             // この時点でのHomeタブの未読件数
393             Assert.Equal(3, homeTab.UnreadCount);
394
395             // Recent タブのツイートをすべて未読にする
396             this.tabinfo.SetReadHomeTab();
397
398             // FilterHit が true である StatusId: 150 を除いてすべて未読になっている
399             Assert.Equal(1, homeTab.UnreadCount);
400             Assert.Equal(150L, homeTab.NextUnreadId);
401         }
402
403         [Fact]
404         public void SubmitUpdate_RemoveSubmit_Test()
405         {
406             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
407
408             this.tabinfo.AddPost(new PostClass { StatusId = 100L });
409             this.tabinfo.DistributePosts();
410             this.tabinfo.SubmitUpdate();
411
412             Assert.Equal(1, homeTab.AllCount);
413
414             this.tabinfo.RemovePostFromAllTabs(100L, setIsDeleted: true);
415
416             // この時点ではまだ削除されない
417             Assert.Equal(1, homeTab.AllCount);
418
419             this.tabinfo.SubmitUpdate(out _, out _, out _, out var isDeletePost);
420
421             Assert.True(isDeletePost);
422             Assert.Equal(0, homeTab.AllCount);
423             Assert.False(this.tabinfo.Posts.ContainsKey(100L));
424         }
425
426         [Fact]
427         public void SubmitUpdate_RemoveSubmit_NotOrphaned_Test()
428         {
429             var homeTab = this.tabinfo.GetTabByType<HomeTabModel>();
430             var favTab = this.tabinfo.GetTabByType<FavoritesTabModel>();
431
432             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsFav = true });
433             this.tabinfo.DistributePosts();
434             this.tabinfo.SubmitUpdate();
435
436             Assert.Equal(1, homeTab.AllCount);
437             Assert.Equal(1, favTab.AllCount);
438
439             // favTab のみ発言を除去 (homeTab には残ったまま)
440             favTab.EnqueueRemovePost(100L, setIsDeleted: false);
441
442             // この時点ではまだ削除されない
443             Assert.Equal(1, homeTab.AllCount);
444             Assert.Equal(1, favTab.AllCount);
445
446             this.tabinfo.SubmitUpdate(out _, out _, out _, out var isDeletePost);
447
448             Assert.True(isDeletePost);
449             Assert.Equal(1, homeTab.AllCount);
450             Assert.Equal(0, favTab.AllCount);
451
452             // homeTab には発言が残っているので Posts からは削除されない
453             Assert.True(this.tabinfo.Posts.ContainsKey(100L));
454         }
455
456         [Fact]
457         public void SubmitUpdate_NotifyPriorityTest()
458         {
459             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
460             homeTab.UnreadManage = true;
461             homeTab.SoundFile = "home.wav";
462
463             var replyTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Mentions);
464             replyTab.UnreadManage = true;
465             replyTab.SoundFile = "reply.wav";
466
467             var dmTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.DirectMessage);
468             dmTab.UnreadManage = true;
469             dmTab.SoundFile = "dm.wav";
470
471             // 通常ツイート
472             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
473
474             // リプライ
475             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
476
477             // DM
478             dmTab.AddPostQueue(new PostClass { StatusId = 300L, IsDm = true, IsRead = false });
479
480             this.tabinfo.DistributePosts();
481
482             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts, out _, out _);
483
484             // DM が最も優先度が高いため DM の通知音が再生される
485             Assert.Equal("dm.wav", soundFile);
486
487             // 通知対象のツイートは 3 件
488             Assert.Equal(3, notifyPosts.Length);
489         }
490
491         [Fact]
492         public void SubmitUpdate_IgnoreEmptySoundPath_Test()
493         {
494             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
495             homeTab.UnreadManage = true;
496             homeTab.SoundFile = "home.wav";
497
498             var replyTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Mentions);
499             replyTab.UnreadManage = true;
500             replyTab.SoundFile = "";
501
502             // 通常ツイート
503             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
504
505             // リプライ
506             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
507
508             this.tabinfo.DistributePosts();
509
510             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts, out _, out _);
511
512             // リプライの方が通知音の優先度が高いが、replyTab.SoundFile が空文字列なので次点の Recent の通知音を鳴らす
513             Assert.Equal("home.wav", soundFile);
514
515             // 通知対象のツイートは 2 件
516             Assert.Equal(2, notifyPosts.Length);
517         }
518
519         [Fact]
520         public void FilterAll_CopyFilterTest()
521         {
522             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
523
524             var myTab1 = new FilterTabModel("MyTab1");
525             this.tabinfo.AddTab(myTab1);
526
527             var filter = new PostFilterRule
528             {
529                 FilterName = "aaa",
530
531                 // コピーのみ
532                 MoveMatches = false,
533                 MarkMatches = false,
534             };
535             myTab1.AddFilter(filter);
536             myTab1.FilterModified = false;
537
538             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
539             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
540             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
541             this.tabinfo.DistributePosts();
542             this.tabinfo.SubmitUpdate();
543
544             // この時点での振り分け状態
545             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
546             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
547
548             // フィルタを変更する
549             filter.FilterName = "bbb";
550
551             // フィルタの変更を反映
552             this.tabinfo.FilterAll();
553             this.tabinfo.DistributePosts();
554             this.tabinfo.SubmitUpdate();
555
556             // 期待する動作:
557             //   [statusId: 100] は MyTab1 から取り除かれる
558             //   [statusId: 200] は Recent から MyTab1 にコピーされる
559
560             // 変更後の振り分け状態
561             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
562             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
563         }
564
565         [Fact]
566         public void FilterAll_CopyAndMarkFilterTest()
567         {
568             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
569
570             var myTab1 = new FilterTabModel("MyTab1");
571             this.tabinfo.AddTab(myTab1);
572
573             var filter = new PostFilterRule
574             {
575                 FilterName = "aaa",
576
577                 // コピー+マーク
578                 MoveMatches = false,
579                 MarkMatches = true,
580             };
581             myTab1.AddFilter(filter);
582             myTab1.FilterModified = false;
583
584             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
585             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
586             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
587             this.tabinfo.DistributePosts();
588             this.tabinfo.SubmitUpdate();
589
590             // この時点での振り分け状態
591             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
592             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
593
594             // フィルタを変更する
595             filter.FilterName = "bbb";
596
597             // フィルタの変更を反映
598             this.tabinfo.FilterAll();
599             this.tabinfo.DistributePosts();
600             this.tabinfo.SubmitUpdate();
601
602             // 期待する動作:
603             //   [statusId: 100] は MyTab1 から取り除かれる
604             //   [statusId: 200] は Recent から MyTab1 にコピーされ、マークが付与される
605
606             // 変更後の振り分け状態
607             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
608             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
609
610             // [statusId: 200] は IsMark が true の状態になる
611             Assert.True(this.tabinfo[200L].IsMark);
612         }
613
614         [Fact]
615         public void FilterAll_MoveFilterTest()
616         {
617             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
618
619             var myTab1 = new FilterTabModel("MyTab1");
620             this.tabinfo.AddTab(myTab1);
621
622             var filter = new PostFilterRule
623             {
624                 FilterName = "aaa",
625
626                 // マッチしたら移動
627                 MoveMatches = true,
628             };
629             myTab1.AddFilter(filter);
630             myTab1.FilterModified = false;
631
632             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
633             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
634             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
635             this.tabinfo.DistributePosts();
636             this.tabinfo.SubmitUpdate();
637
638             // この時点での振り分け状態
639             Assert.Equal(new[] { 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
640             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
641
642             // フィルタを変更する
643             filter.FilterName = "bbb";
644
645             // フィルタの変更を反映
646             this.tabinfo.FilterAll();
647             this.tabinfo.DistributePosts();
648             this.tabinfo.SubmitUpdate();
649
650             // 期待する動作:
651             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
652             //   [statusId: 200] は Recent から MyTab1 に移動される
653
654             // 変更後の振り分け状態
655             Assert.Equal(new[] { 100L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
656             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
657         }
658
659         [Fact]
660         public void FilterAll_MoveFilterTest2()
661         {
662             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
663
664             var myTab1 = new FilterTabModel("MyTab1");
665             var myTab2 = new FilterTabModel("MyTab2");
666             this.tabinfo.AddTab(myTab1);
667             this.tabinfo.AddTab(myTab2);
668
669             var filter1 = new PostFilterRule
670             {
671                 FilterName = "aaa",
672
673                 // マッチしたら移動
674                 MoveMatches = true,
675             };
676             myTab1.AddFilter(filter1);
677             myTab1.FilterModified = false;
678
679             var filter2 = new PostFilterRule
680             {
681                 FilterName = "bbb",
682
683                 // マッチしたら移動
684                 MoveMatches = true,
685             };
686             myTab2.AddFilter(filter2);
687             myTab2.FilterModified = false;
688
689             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
690             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
691             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
692             this.tabinfo.DistributePosts();
693             this.tabinfo.SubmitUpdate();
694
695             // この時点での振り分け状態
696             Assert.Equal(new[] { 300L }, homeTab.StatusIds);
697             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
698             Assert.Equal(new[] { 200L }, myTab2.StatusIds);
699
700             // MyTab1 のフィルタを変更する
701             filter1.FilterName = "bbb";
702
703             // MyTab2 のフィルタを変更する
704             filter2.FilterName = "ccc";
705
706             // フィルタの変更を反映
707             this.tabinfo.FilterAll();
708             this.tabinfo.DistributePosts();
709             this.tabinfo.SubmitUpdate();
710
711             // 期待する動作:
712             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
713             //   [statusId: 200] は MyTab1 に移動される
714             //   [statusId: 200] は MyTab2 から取り除かれるが MyTab1 に移動されているので Recent には戻さない
715             //   [statusId: 300] は Recent から MyTab2 に移動される
716
717             // 変更後の振り分け状態
718             Assert.Equal(new[] { 100L }, homeTab.StatusIds);
719             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
720             Assert.Equal(new[] { 300L }, myTab2.StatusIds);
721         }
722
723         [Fact]
724         public void FilterAll_ExcludeReplyFilterTest()
725         {
726             var homeTab = this.tabinfo.GetTabByType<HomeTabModel>();
727             var replyTab = this.tabinfo.GetTabByType<MentionsTabModel>();
728
729             var filter = new PostFilterRule
730             {
731                 // @aaa からのリプライは Reply タブに振り分けない
732                 ExFilterName = "aaa",
733             };
734             replyTab.AddFilter(filter);
735             replyTab.FilterModified = false;
736
737             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa", IsReply = true });
738             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb", IsReply = true });
739             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc", IsReply = true });
740             this.tabinfo.DistributePosts();
741             this.tabinfo.SubmitUpdate();
742
743             // この時点での振り分け状態
744             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
745             Assert.Equal(new[] { 200L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
746
747             // [statusId: 100] は IsExcludeReply が true の状態になっている
748             Assert.True(this.tabinfo[100L].IsExcludeReply);
749
750             // Reply のフィルタを変更する
751             filter.ExFilterName = "bbb";
752
753             // フィルタの変更を反映
754             this.tabinfo.FilterAll();
755             this.tabinfo.DistributePosts();
756             this.tabinfo.SubmitUpdate();
757
758             // 期待する動作:
759             //   [statusId: 100] は Reply にコピーされ、IsExcludeReply が false になる
760             //   [statusId: 200] は Reply から取り除かれ、IsExcludeReply が true になる
761
762             // 変更後の振り分け状態
763             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
764             Assert.Equal(new[] { 100L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
765
766             // [statusId: 100] は IsExcludeReply が false の状態になる
767             Assert.False(this.tabinfo[100L].IsExcludeReply);
768
769             // [statusId: 200] は IsExcludeReply が true の状態になる
770             Assert.True(this.tabinfo[200L].IsExcludeReply);
771         }
772
773         class TestPostFilterRule : PostFilterRule
774         {
775             public static PostFilterRule Create(Func<PostClass, MyCommon.HITRESULT> filterDelegate)
776             {
777                 return new TestPostFilterRule
778                 {
779                     FilterDelegate = filterDelegate,
780                     IsDirty = false,
781                 };
782             }
783         }
784     }
785 }