OSDN Git Service

Merge branch 'selected-state'
[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 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.Values.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 var soundFile, out var notifyPosts,
420                 out var newMentionOrDm, out var isDeletePost);
421
422             Assert.True(isDeletePost);
423             Assert.Equal(0, homeTab.AllCount);
424             Assert.False(this.tabinfo.Posts.ContainsKey(100L));
425         }
426
427         [Fact]
428         public void SubmitUpdate_RemoveSubmit_NotOrphaned_Test()
429         {
430             var homeTab = this.tabinfo.GetTabByType<HomeTabModel>();
431             var favTab = this.tabinfo.GetTabByType<FavoritesTabModel>();
432
433             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsFav = true });
434             this.tabinfo.DistributePosts();
435             this.tabinfo.SubmitUpdate();
436
437             Assert.Equal(1, homeTab.AllCount);
438             Assert.Equal(1, favTab.AllCount);
439
440             // favTab のみ発言を除去 (homeTab には残ったまま)
441             favTab.EnqueueRemovePost(100L, setIsDeleted: false);
442
443             // この時点ではまだ削除されない
444             Assert.Equal(1, homeTab.AllCount);
445             Assert.Equal(1, favTab.AllCount);
446
447             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts,
448                 out var newMentionOrDm, out var isDeletePost);
449
450             Assert.True(isDeletePost);
451             Assert.Equal(1, homeTab.AllCount);
452             Assert.Equal(0, favTab.AllCount);
453
454             // homeTab には発言が残っているので Posts からは削除されない
455             Assert.True(this.tabinfo.Posts.ContainsKey(100L));
456         }
457
458         [Fact]
459         public void SubmitUpdate_NotifyPriorityTest()
460         {
461             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
462             homeTab.UnreadManage = true;
463             homeTab.SoundFile = "home.wav";
464
465             var replyTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Mentions);
466             replyTab.UnreadManage = true;
467             replyTab.SoundFile = "reply.wav";
468
469             var dmTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.DirectMessage);
470             dmTab.UnreadManage = true;
471             dmTab.SoundFile = "dm.wav";
472
473             // 通常ツイート
474             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
475
476             // リプライ
477             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
478
479             // DM
480             dmTab.AddPostQueue(new PostClass { StatusId = 300L, IsDm = true, IsRead = false });
481
482             this.tabinfo.DistributePosts();
483
484             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts,
485                 out var newMentionOrDm, out var isDeletePost);
486
487             // DM が最も優先度が高いため DM の通知音が再生される
488             Assert.Equal("dm.wav", soundFile);
489
490             // 通知対象のツイートは 3 件
491             Assert.Equal(3, notifyPosts.Length);
492         }
493
494         [Fact]
495         public void SubmitUpdate_IgnoreEmptySoundPath_Test()
496         {
497             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
498             homeTab.UnreadManage = true;
499             homeTab.SoundFile = "home.wav";
500
501             var replyTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Mentions);
502             replyTab.UnreadManage = true;
503             replyTab.SoundFile = "";
504
505             // 通常ツイート
506             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
507
508             // リプライ
509             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
510
511             this.tabinfo.DistributePosts();
512
513             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts,
514                 out var newMentionOrDm, out var isDeletePost);
515
516             // リプライの方が通知音の優先度が高いが、replyTab.SoundFile が空文字列なので次点の Recent の通知音を鳴らす
517             Assert.Equal("home.wav", soundFile);
518
519             // 通知対象のツイートは 2 件
520             Assert.Equal(2, notifyPosts.Length);
521         }
522
523         [Fact]
524         public void FilterAll_CopyFilterTest()
525         {
526             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
527
528             var myTab1 = new FilterTabModel("MyTab1");
529             this.tabinfo.AddTab(myTab1);
530
531             var filter = new PostFilterRule
532             {
533                 FilterName = "aaa",
534
535                 // コピーのみ
536                 MoveMatches = false,
537                 MarkMatches = false,
538             };
539             myTab1.AddFilter(filter);
540             myTab1.FilterModified = false;
541
542             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
543             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
544             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
545             this.tabinfo.DistributePosts();
546             this.tabinfo.SubmitUpdate();
547
548             // この時点での振り分け状態
549             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
550             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
551
552             // フィルタを変更する
553             filter.FilterName = "bbb";
554
555             // フィルタの変更を反映
556             this.tabinfo.FilterAll();
557             this.tabinfo.DistributePosts();
558             this.tabinfo.SubmitUpdate();
559
560             // 期待する動作:
561             //   [statusId: 100] は MyTab1 から取り除かれる
562             //   [statusId: 200] は Recent から MyTab1 にコピーされる
563
564             // 変更後の振り分け状態
565             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
566             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
567         }
568
569         [Fact]
570         public void FilterAll_CopyAndMarkFilterTest()
571         {
572             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
573
574             var myTab1 = new FilterTabModel("MyTab1");
575             this.tabinfo.AddTab(myTab1);
576
577             var filter = new PostFilterRule
578             {
579                 FilterName = "aaa",
580
581                 // コピー+マーク
582                 MoveMatches = false,
583                 MarkMatches = true,
584             };
585             myTab1.AddFilter(filter);
586             myTab1.FilterModified = false;
587
588             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
589             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
590             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
591             this.tabinfo.DistributePosts();
592             this.tabinfo.SubmitUpdate();
593
594             // この時点での振り分け状態
595             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
596             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
597
598             // フィルタを変更する
599             filter.FilterName = "bbb";
600
601             // フィルタの変更を反映
602             this.tabinfo.FilterAll();
603             this.tabinfo.DistributePosts();
604             this.tabinfo.SubmitUpdate();
605
606             // 期待する動作:
607             //   [statusId: 100] は MyTab1 から取り除かれる
608             //   [statusId: 200] は Recent から MyTab1 にコピーされ、マークが付与される
609
610             // 変更後の振り分け状態
611             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
612             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
613
614             // [statusId: 200] は IsMark が true の状態になる
615             Assert.True(this.tabinfo[200L].IsMark);
616         }
617
618         [Fact]
619         public void FilterAll_MoveFilterTest()
620         {
621             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
622
623             var myTab1 = new FilterTabModel("MyTab1");
624             this.tabinfo.AddTab(myTab1);
625
626             var filter = new PostFilterRule
627             {
628                 FilterName = "aaa",
629
630                 // マッチしたら移動
631                 MoveMatches = true,
632             };
633             myTab1.AddFilter(filter);
634             myTab1.FilterModified = false;
635
636             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
637             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
638             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
639             this.tabinfo.DistributePosts();
640             this.tabinfo.SubmitUpdate();
641
642             // この時点での振り分け状態
643             Assert.Equal(new[] { 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
644             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
645
646             // フィルタを変更する
647             filter.FilterName = "bbb";
648
649             // フィルタの変更を反映
650             this.tabinfo.FilterAll();
651             this.tabinfo.DistributePosts();
652             this.tabinfo.SubmitUpdate();
653
654             // 期待する動作:
655             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
656             //   [statusId: 200] は Recent から MyTab1 に移動される
657
658             // 変更後の振り分け状態
659             Assert.Equal(new[] { 100L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
660             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
661         }
662
663         [Fact]
664         public void FilterAll_MoveFilterTest2()
665         {
666             var homeTab = this.tabinfo.GetTabByType(MyCommon.TabUsageType.Home);
667
668             var myTab1 = new FilterTabModel("MyTab1");
669             var myTab2 = new FilterTabModel("MyTab2");
670             this.tabinfo.AddTab(myTab1);
671             this.tabinfo.AddTab(myTab2);
672
673             var filter1 = new PostFilterRule
674             {
675                 FilterName = "aaa",
676
677                 // マッチしたら移動
678                 MoveMatches = true,
679             };
680             myTab1.AddFilter(filter1);
681             myTab1.FilterModified = false;
682
683             var filter2 = new PostFilterRule
684             {
685                 FilterName = "bbb",
686
687                 // マッチしたら移動
688                 MoveMatches = true,
689             };
690             myTab2.AddFilter(filter2);
691             myTab2.FilterModified = false;
692
693             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
694             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
695             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
696             this.tabinfo.DistributePosts();
697             this.tabinfo.SubmitUpdate();
698
699             // この時点での振り分け状態
700             Assert.Equal(new[] { 300L }, homeTab.StatusIds);
701             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
702             Assert.Equal(new[] { 200L }, myTab2.StatusIds);
703
704             // MyTab1 のフィルタを変更する
705             filter1.FilterName = "bbb";
706
707             // MyTab2 のフィルタを変更する
708             filter2.FilterName = "ccc";
709
710             // フィルタの変更を反映
711             this.tabinfo.FilterAll();
712             this.tabinfo.DistributePosts();
713             this.tabinfo.SubmitUpdate();
714
715             // 期待する動作:
716             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
717             //   [statusId: 200] は MyTab1 に移動される
718             //   [statusId: 200] は MyTab2 から取り除かれるが MyTab1 に移動されているので Recent には戻さない
719             //   [statusId: 300] は Recent から MyTab2 に移動される
720
721             // 変更後の振り分け状態
722             Assert.Equal(new[] { 100L }, homeTab.StatusIds);
723             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
724             Assert.Equal(new[] { 300L }, myTab2.StatusIds);
725         }
726
727         [Fact]
728         public void FilterAll_ExcludeReplyFilterTest()
729         {
730             var homeTab = this.tabinfo.GetTabByType<HomeTabModel>();
731             var replyTab = this.tabinfo.GetTabByType<MentionsTabModel>();
732
733             var filter = new PostFilterRule
734             {
735                 // @aaa からのリプライは Reply タブに振り分けない
736                 ExFilterName = "aaa",
737             };
738             replyTab.AddFilter(filter);
739             replyTab.FilterModified = false;
740
741             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa", IsReply = true });
742             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb", IsReply = true });
743             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc", IsReply = true });
744             this.tabinfo.DistributePosts();
745             this.tabinfo.SubmitUpdate();
746
747             // この時点での振り分け状態
748             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
749             Assert.Equal(new[] { 200L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
750
751             // [statusId: 100] は IsExcludeReply が true の状態になっている
752             Assert.True(this.tabinfo[100L].IsExcludeReply);
753
754             // Reply のフィルタを変更する
755             filter.ExFilterName = "bbb";
756
757             // フィルタの変更を反映
758             this.tabinfo.FilterAll();
759             this.tabinfo.DistributePosts();
760             this.tabinfo.SubmitUpdate();
761
762             // 期待する動作:
763             //   [statusId: 100] は Reply にコピーされ、IsExcludeReply が false になる
764             //   [statusId: 200] は Reply から取り除かれ、IsExcludeReply が true になる
765
766             // 変更後の振り分け状態
767             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
768             Assert.Equal(new[] { 100L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
769
770             // [statusId: 100] は IsExcludeReply が false の状態になる
771             Assert.False(this.tabinfo[100L].IsExcludeReply);
772
773             // [statusId: 200] は IsExcludeReply が true の状態になる
774             Assert.True(this.tabinfo[200L].IsExcludeReply);
775         }
776
777         class TestPostFilterRule : PostFilterRule
778         {
779             public static PostFilterRule Create(Func<PostClass, MyCommon.HITRESULT> filterDelegate)
780             {
781                 return new TestPostFilterRule
782                 {
783                     FilterDelegate = filterDelegate,
784                     IsDirty = false,
785                 };
786             }
787         }
788     }
789 }