OSDN Git Service

Merge pull request #204 from opentween/add-testcases
[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 System.Windows.Forms;
28 using Xunit;
29 using Xunit.Extensions;
30
31 namespace OpenTween.Models
32 {
33     public class TabInformationTest
34     {
35         private readonly TabInformations tabinfo;
36
37         public TabInformationTest()
38         {
39             this.tabinfo = this.CreateInstance();
40
41             // TabInformation.GetInstance() で取得できるようにする
42             var field = typeof(TabInformations).GetField("Instance",
43                 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField);
44             field.SetValue(null, this.tabinfo);
45
46             // 標準のタブを追加
47             this.tabinfo.AddTab(new HomeTabModel("Recent"));
48             this.tabinfo.AddTab(new MentionsTabModel("Reply"));
49             this.tabinfo.AddTab(new DirectMessagesTabModel("DM"));
50             this.tabinfo.AddTab(new FavoritesTabModel("Favorites"));
51         }
52
53         private TabInformations CreateInstance()
54             => (TabInformations)Activator.CreateInstance(typeof(TabInformations), true);
55
56         [Fact]
57         public void AddTab_Test()
58         {
59             var tab = new FilterTabModel("MyTab");
60             var ret = this.tabinfo.AddTab(tab);
61
62             Assert.True(ret);
63             Assert.Same(tab, this.tabinfo.Tabs.Last());
64         }
65
66         [Fact]
67         public void AddTab_DuplicateTest()
68         {
69             var ret = this.tabinfo.AddTab(new FilterTabModel("Recent"));
70
71             Assert.False(ret);
72         }
73
74         [Fact]
75         public void RemoveTab_InnerStorageTabTest()
76         {
77             var tab = new PublicSearchTabModel("search");
78             tab.AddPostQueue(new PostClass { StatusId = 100L });
79             this.tabinfo.AddTab(tab);
80             this.tabinfo.SubmitUpdate();
81
82             Assert.True(this.tabinfo.ContainsTab("search"));
83             Assert.Empty(this.tabinfo.RemovedTab);
84
85             this.tabinfo.RemoveTab("search");
86
87             Assert.False(this.tabinfo.ContainsTab("search"));
88             Assert.Single(this.tabinfo.RemovedTab);
89             Assert.Contains(tab, this.tabinfo.RemovedTab);
90         }
91
92         [Fact]
93         public void RemoveTab_FilterTab_MovedPost_OrphanedTest()
94         {
95             var filterTab = new FilterTabModel("filter");
96             filterTab.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
97             this.tabinfo.AddTab(filterTab);
98
99             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "opentween" });
100             this.tabinfo.DistributePosts();
101             this.tabinfo.SubmitUpdate();
102
103             Assert.False(this.tabinfo.HomeTab.Contains(100L));
104             Assert.True(filterTab.Contains(100L));
105
106             this.tabinfo.RemoveTab("filter");
107
108             Assert.False(this.tabinfo.ContainsTab("filter"));
109             Assert.Single(this.tabinfo.RemovedTab);
110             Assert.Contains(filterTab, this.tabinfo.RemovedTab);
111
112             // 他に MoveMatches で移動している振り分けタブが存在しなければ Home タブに戻す
113             Assert.True(this.tabinfo.HomeTab.Contains(100L));
114         }
115
116         [Fact]
117         public void RemoveTab_FilterTab_MovedPost_NotOrphanedTest()
118         {
119             var filterTab1 = new FilterTabModel("filter1");
120             filterTab1.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
121             this.tabinfo.AddTab(filterTab1);
122
123             var filterTab2 = new FilterTabModel("filter2");
124             filterTab2.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
125             this.tabinfo.AddTab(filterTab2);
126
127             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "opentween" });
128             this.tabinfo.DistributePosts();
129             this.tabinfo.SubmitUpdate();
130
131             Assert.False(this.tabinfo.HomeTab.Contains(100L));
132             Assert.True(filterTab1.Contains(100L));
133             Assert.True(filterTab2.Contains(100L));
134
135             this.tabinfo.RemoveTab("filter1");
136
137             Assert.False(this.tabinfo.ContainsTab("filter1"));
138             Assert.Single(this.tabinfo.RemovedTab);
139             Assert.Contains(filterTab1, this.tabinfo.RemovedTab);
140
141             // 他に MoveMatches で移動している振り分けタブが存在する場合は Home タブに戻さない
142             Assert.False(this.tabinfo.HomeTab.Contains(100L));
143             Assert.True(filterTab2.Contains(100L));
144         }
145
146         [Fact]
147         public void RemoveTab_FilterTab_CopiedPost_Test()
148         {
149             var filterTab = new FilterTabModel("filter");
150             filterTab.AddFilter(new() { FilterName = "opentween", MoveMatches = false });
151             this.tabinfo.AddTab(filterTab);
152
153             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "opentween" });
154             this.tabinfo.DistributePosts();
155             this.tabinfo.SubmitUpdate();
156
157             Assert.True(this.tabinfo.HomeTab.Contains(100L));
158             Assert.True(filterTab.Contains(100L));
159
160             this.tabinfo.RemoveTab("filter");
161
162             Assert.False(this.tabinfo.ContainsTab("filter"));
163             Assert.Single(this.tabinfo.RemovedTab);
164             Assert.Contains(filterTab, this.tabinfo.RemovedTab);
165
166             // 振り分けタブにコピーされた発言は Home タブにも存在しているため何もしない
167             Assert.True(this.tabinfo.HomeTab.Contains(100L));
168         }
169
170         [Fact]
171         public void CanUndoRemovedTab_Test()
172         {
173             var tab = new PublicSearchTabModel("tab");
174             this.tabinfo.AddTab(tab);
175             Assert.False(this.tabinfo.CanUndoRemovedTab);
176
177             this.tabinfo.RemoveTab(tab.TabName);
178             Assert.True(this.tabinfo.CanUndoRemovedTab);
179         }
180
181         [Fact]
182         public void UndoRemovedTab_Test()
183         {
184             var tab = new PublicSearchTabModel("tab");
185             this.tabinfo.AddTab(tab);
186             Assert.True(this.tabinfo.ContainsTab("tab"));
187             Assert.Empty(this.tabinfo.RemovedTab);
188
189             this.tabinfo.RemoveTab("tab");
190             Assert.False(this.tabinfo.ContainsTab("tab"));
191             Assert.Single(this.tabinfo.RemovedTab);
192
193             var restoredTab = this.tabinfo.UndoRemovedTab();
194             Assert.True(this.tabinfo.ContainsTab("tab"));
195             Assert.Same(tab, restoredTab);
196             Assert.Empty(this.tabinfo.RemovedTab);
197         }
198
199         [Fact]
200         public void UndoRemovedTab_EmptyError_Test()
201         {
202             Assert.Empty(this.tabinfo.RemovedTab);
203             Assert.Throws<TabException>(
204                 () => this.tabinfo.UndoRemovedTab()
205             );
206         }
207
208         [Fact]
209         public void UndoRemovedTab_DuplicatedName_Test()
210         {
211             var tab = new PublicSearchTabModel("tab");
212             this.tabinfo.AddTab(tab);
213             Assert.Empty(this.tabinfo.RemovedTab);
214
215             this.tabinfo.RemoveTab("tab");
216             Assert.Single(this.tabinfo.RemovedTab);
217
218             this.tabinfo.RenameTab("Recent", "tab");
219             Assert.Throws<TabException>(
220                 () => this.tabinfo.UndoRemovedTab()
221             );
222             Assert.Single(this.tabinfo.RemovedTab);
223         }
224
225         [Fact]
226         public void RenameTab_PositionTest()
227         {
228             var replyTab = this.tabinfo.Tabs["Reply"];
229             Assert.Equal(1, this.tabinfo.Tabs.IndexOf(replyTab));
230
231             this.tabinfo.RenameTab("Reply", "Reply12345");
232
233             Assert.Equal("Reply12345", replyTab.TabName);
234             Assert.Equal(1, this.tabinfo.Tabs.IndexOf(replyTab));
235         }
236
237         [Fact]
238         public void RenameTab_SelectedTabTest()
239         {
240             var replyTab = this.tabinfo.Tabs["Reply"];
241
242             this.tabinfo.SelectTab("Reply");
243             this.tabinfo.RenameTab("Reply", "Reply12345");
244
245             Assert.Equal("Reply12345", replyTab.TabName);
246             Assert.Equal("Reply12345", this.tabinfo.SelectedTabName);
247             Assert.Equal(replyTab, this.tabinfo.SelectedTab);
248         }
249
250         [Fact]
251         public void MoveTab_MoveToStart_Test()
252         {
253             Assert.Equal(0, this.tabinfo.Tabs.IndexOf("Recent"));
254             Assert.Equal(1, this.tabinfo.Tabs.IndexOf("Reply"));
255
256             this.tabinfo.MoveTab(0, this.tabinfo.MentionTab);
257
258             Assert.Equal(0, this.tabinfo.Tabs.IndexOf("Reply"));
259             Assert.Equal(1, this.tabinfo.Tabs.IndexOf("Recent"));
260         }
261
262         [Fact]
263         public void MoveTab_MoveToEnd_Test()
264         {
265             Assert.Equal(4, this.tabinfo.Tabs.Count);
266             Assert.Equal(2, this.tabinfo.Tabs.IndexOf("DM"));
267             Assert.Equal(3, this.tabinfo.Tabs.IndexOf("Favorites"));
268
269             this.tabinfo.MoveTab(3, this.tabinfo.DirectMessageTab);
270
271             Assert.Equal(2, this.tabinfo.Tabs.IndexOf("Favorites"));
272             Assert.Equal(3, this.tabinfo.Tabs.IndexOf("DM"));
273         }
274
275         [Fact]
276         public void MoveTab_OutOfRangeError_Test()
277         {
278             Assert.Equal(4, this.tabinfo.Tabs.Count);
279             Assert.Throws<ArgumentOutOfRangeException>(
280                 () => this.tabinfo.MoveTab(-1, this.tabinfo.HomeTab)
281             );
282             Assert.Throws<ArgumentOutOfRangeException>(
283                 () => this.tabinfo.MoveTab(4, this.tabinfo.HomeTab)
284             );
285         }
286
287         [Theory]
288         [InlineData("Reply", true)]
289         [InlineData("UNKNOWN NAME", false)]
290         public void ContainsTab_TabName_Test(string tabName, bool expected)
291             => Assert.Equal(expected, this.tabinfo.ContainsTab(tabName));
292
293         [Fact]
294         public void ContainsTab_TabInstance_Test()
295         {
296             Assert.True(this.tabinfo.ContainsTab(this.tabinfo.HomeTab));
297             Assert.False(this.tabinfo.ContainsTab(new PublicSearchTabModel("tab")));
298         }
299
300         [Fact]
301         public void SelectTab_Test()
302         {
303             this.tabinfo.SelectTab("Reply");
304
305             Assert.Equal("Reply", this.tabinfo.SelectedTabName);
306             Assert.IsType<MentionsTabModel>(this.tabinfo.SelectedTab);
307         }
308
309         [Fact]
310         public void SelectTab_NotExistTest()
311             => Assert.Throws<ArgumentException>(() => this.tabinfo.SelectTab("INVALID"));
312
313         [Theory]
314         [InlineData(MyCommon.TabUsageType.Home, typeof(HomeTabModel))]
315         [InlineData(MyCommon.TabUsageType.Mentions, typeof(MentionsTabModel))]
316         [InlineData(MyCommon.TabUsageType.DirectMessage, typeof(DirectMessagesTabModel))]
317         [InlineData(MyCommon.TabUsageType.Favorites, typeof(FavoritesTabModel))]
318         [InlineData(MyCommon.TabUsageType.UserDefined, typeof(FilterTabModel))]
319         [InlineData(MyCommon.TabUsageType.UserTimeline, typeof(UserTimelineTabModel))]
320         [InlineData(MyCommon.TabUsageType.PublicSearch, typeof(PublicSearchTabModel))]
321         [InlineData(MyCommon.TabUsageType.Lists, typeof(ListTimelineTabModel))]
322         [InlineData(MyCommon.TabUsageType.Mute, typeof(MuteTabModel))]
323         public void CreateTabFromSettings_TabTypeTest(MyCommon.TabUsageType tabType, Type expected)
324         {
325             var tabSetting = new SettingTabs.SettingTabItem
326             {
327                 TabName = "tab",
328                 TabType = tabType,
329             };
330             var tabinfo = this.CreateInstance();
331             var tab = tabinfo.CreateTabFromSettings(tabSetting);
332             Assert.IsType(expected, tab);
333         }
334
335         [Fact]
336         public void CreateTabFromSettings_FilterTabTest()
337         {
338             var tabSetting = new SettingTabs.SettingTabItem
339             {
340                 TabName = "tab",
341                 TabType = MyCommon.TabUsageType.UserDefined,
342                 FilterArray = new PostFilterRule[]
343                 {
344                     new() { FilterName = "foo" },
345                 },
346             };
347             var tabinfo = this.CreateInstance();
348             var tab = tabinfo.CreateTabFromSettings(tabSetting);
349             Assert.IsType<FilterTabModel>(tab);
350
351             var filterTab = (FilterTabModel)tab!;
352             Assert.Equal("foo", filterTab.FilterArray.First().FilterName);
353         }
354
355         [Fact]
356         public void CreateTabFromSettings_PublicSearchTabTest()
357         {
358             var tabSetting = new SettingTabs.SettingTabItem
359             {
360                 TabName = "tab",
361                 TabType = MyCommon.TabUsageType.PublicSearch,
362                 SearchWords = "foo",
363                 SearchLang = "ja",
364             };
365             var tabinfo = this.CreateInstance();
366             var tab = tabinfo.CreateTabFromSettings(tabSetting);
367             Assert.IsType<PublicSearchTabModel>(tab);
368
369             var searchTab = (PublicSearchTabModel)tab!;
370             Assert.Equal("foo", searchTab.SearchWords);
371             Assert.Equal("ja", searchTab.SearchLang);
372         }
373
374         [Fact]
375         public void AddDefaultTabs_Test()
376         {
377             var tabinfo = this.CreateInstance();
378             Assert.Equal(0, tabinfo.Tabs.Count);
379
380             tabinfo.AddDefaultTabs();
381
382             Assert.Equal(4, tabinfo.Tabs.Count);
383             Assert.IsType<HomeTabModel>(tabinfo.Tabs[0]);
384             Assert.IsType<MentionsTabModel>(tabinfo.Tabs[1]);
385             Assert.IsType<DirectMessagesTabModel>(tabinfo.Tabs[2]);
386             Assert.IsType<FavoritesTabModel>(tabinfo.Tabs[3]);
387
388             var homeTab = tabinfo.HomeTab;
389             Assert.False(homeTab.Notify);
390
391             var mentionsTab = tabinfo.MentionTab;
392             Assert.True(mentionsTab.Notify);
393
394             var dmTab = tabinfo.DirectMessageTab;
395             Assert.True(dmTab.Notify);
396
397             var favsTab = tabinfo.FavoriteTab;
398             Assert.False(favsTab.Notify);
399         }
400
401         [Fact]
402         public void MakeTabName_Test()
403         {
404             var baseTabName = "NewTab";
405             Assert.Equal("NewTab", this.tabinfo.MakeTabName(baseTabName, 5));
406         }
407
408         [Fact]
409         public void MakeTabName_RetryTest()
410         {
411             this.tabinfo.AddTab(new FilterTabModel("NewTab"));
412             this.tabinfo.AddTab(new FilterTabModel("NewTab2"));
413
414             var baseTabName = "NewTab";
415             Assert.Equal("NewTab3", this.tabinfo.MakeTabName(baseTabName, 5));
416         }
417
418         [Fact]
419         public void MakeTabName_RetryErrorTest()
420         {
421             this.tabinfo.AddTab(new FilterTabModel("NewTab"));
422             this.tabinfo.AddTab(new FilterTabModel("NewTab2"));
423             this.tabinfo.AddTab(new FilterTabModel("NewTab3"));
424             this.tabinfo.AddTab(new FilterTabModel("NewTab4"));
425             this.tabinfo.AddTab(new FilterTabModel("NewTab5"));
426
427             var baseTabName = "NewTab";
428             Assert.Throws<TabException>(() => this.tabinfo.MakeTabName(baseTabName, 5));
429         }
430
431         [Fact]
432         public void SetSortMode_Test()
433         {
434             this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
435             Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
436             Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
437             Assert.Equal(ComparerMode.Id, this.tabinfo.HomeTab.SortMode);
438             Assert.Equal(SortOrder.Descending, this.tabinfo.HomeTab.SortOrder);
439
440             this.tabinfo.SetSortMode(ComparerMode.Source, SortOrder.Ascending);
441             Assert.Equal(ComparerMode.Source, this.tabinfo.SortMode);
442             Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
443             Assert.Equal(ComparerMode.Source, this.tabinfo.HomeTab.SortMode);
444             Assert.Equal(SortOrder.Ascending, this.tabinfo.HomeTab.SortOrder);
445         }
446
447         [Fact]
448         public void ToggleSortOrder_SameMode_Test()
449         {
450             this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
451             Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
452             Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
453
454             this.tabinfo.ToggleSortOrder(ComparerMode.Id);
455             Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
456             Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
457
458             this.tabinfo.ToggleSortOrder(ComparerMode.Id);
459             Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
460             Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
461         }
462
463         [Fact]
464         public void ToggleSortOrder_OtherMode_Test()
465         {
466             this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
467             Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
468             Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
469
470             this.tabinfo.ToggleSortOrder(ComparerMode.Source);
471             Assert.Equal(ComparerMode.Source, this.tabinfo.SortMode);
472             Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
473         }
474
475         [Fact]
476         public void IsMuted_Test()
477         {
478             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
479
480             var post = new PostClass
481             {
482                 UserId = 12345L,
483                 Text = "hogehoge",
484             };
485             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
486         }
487
488         [Fact]
489         public void IsMuted_NotMutingTest()
490         {
491             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
492
493             var post = new PostClass
494             {
495                 UserId = 11111L,
496                 Text = "hogehoge",
497             };
498             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
499         }
500
501         [Fact]
502         public void IsMuted_RetweetTest()
503         {
504             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
505
506             var post = new PostClass
507             {
508                 UserId = 11111L,
509                 RetweetedByUserId = 12345L,
510                 Text = "hogehoge",
511             };
512             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
513         }
514
515         [Fact]
516         public void IsMuted_RetweetNotMutingTest()
517         {
518             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
519
520             var post = new PostClass
521             {
522                 UserId = 11111L,
523                 RetweetedByUserId = 22222L,
524                 Text = "hogehoge",
525             };
526             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
527         }
528
529         [Fact]
530         public void IsMuted_ReplyTest()
531         {
532             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
533
534             // ミュート対象のユーザーであってもリプライの場合は対象外とする
535             var post = new PostClass
536             {
537                 UserId = 12345L,
538                 Text = "@foo hogehoge",
539                 IsReply = true,
540             };
541             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
542         }
543
544         [Fact]
545         public void IsMuted_NotInHomeTimelineTest()
546         {
547             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
548
549             // Recent以外のタブ(検索など)の場合は対象外とする
550             var post = new PostClass
551             {
552                 UserId = 12345L,
553                 Text = "hogehoge",
554             };
555             Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: false));
556         }
557
558         [Fact]
559         public void IsMuted_MuteTabRulesTest()
560         {
561             this.tabinfo.MuteUserIds = new HashSet<long> { };
562
563             var muteTab = new MuteTabModel();
564             muteTab.AddFilter(new PostFilterRule
565             {
566                 FilterName = "foo",
567                 MoveMatches = true,
568             });
569             this.tabinfo.AddTab(muteTab);
570
571             var post = new PostClass
572             {
573                 UserId = 12345L,
574                 ScreenName = "foo",
575                 Text = "hogehoge",
576             };
577             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
578         }
579
580         [Fact]
581         public void IsMuted_MuteTabRules_NotInHomeTimelineTest()
582         {
583             this.tabinfo.MuteUserIds = new HashSet<long> { };
584
585             var muteTab = new MuteTabModel();
586             muteTab.AddFilter(new PostFilterRule
587             {
588                 FilterName = "foo",
589                 MoveMatches = true,
590             });
591             this.tabinfo.AddTab(muteTab);
592
593             // ミュートタブによるミュートはリプライも対象とする
594             var post = new PostClass
595             {
596                 UserId = 12345L,
597                 ScreenName = "foo",
598                 Text = "@hoge hogehoge",
599                 IsReply = true,
600             };
601             Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: false));
602         }
603
604         [Fact]
605         public void SetReadAllTab_MarkAsReadTest()
606         {
607             var tab1 = new PublicSearchTabModel("search1");
608             var tab2 = new PublicSearchTabModel("search2");
609
610             this.tabinfo.AddTab(tab1);
611             this.tabinfo.AddTab(tab2);
612
613             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて未読)
614             tab1.UnreadManage = true;
615             tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = false });
616             tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
617             tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
618
619             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて未読)
620             tab2.UnreadManage = true;
621             tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
622             tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
623             tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = false });
624
625             this.tabinfo.DistributePosts();
626             this.tabinfo.SubmitUpdate();
627
628             // この時点での各タブの未読件数
629             Assert.Equal(3, tab1.UnreadCount);
630             Assert.Equal(3, tab2.UnreadCount);
631
632             // ... ここまで長い前置き
633
634             // StatusId: 200 を既読にする (search1, search2 両方に含まれる)
635             this.tabinfo.SetReadAllTab(200L, read: true);
636             Assert.Equal(2, tab1.UnreadCount);
637             Assert.Equal(2, tab2.UnreadCount);
638
639             // StatusId: 100 を既読にする (search1 のみに含まれる)
640             this.tabinfo.SetReadAllTab(100L, read: true);
641             Assert.Equal(1, tab1.UnreadCount);
642             Assert.Equal(2, tab2.UnreadCount);
643         }
644
645         [Fact]
646         public void SetReadAllTab_MarkAsUnreadTest()
647         {
648             var tab1 = new PublicSearchTabModel("search1");
649             var tab2 = new PublicSearchTabModel("search2");
650
651             this.tabinfo.AddTab(tab1);
652             this.tabinfo.AddTab(tab2);
653
654             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて既読)
655             tab1.UnreadManage = true;
656             tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = true });
657             tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
658             tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
659
660             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて既読)
661             tab2.UnreadManage = true;
662             tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
663             tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
664             tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = true });
665
666             this.tabinfo.DistributePosts();
667             this.tabinfo.SubmitUpdate();
668
669             // この時点での各タブの未読件数
670             Assert.Equal(0, tab1.UnreadCount);
671             Assert.Equal(0, tab2.UnreadCount);
672
673             // ... ここまで長い前置き
674
675             // StatusId: 200 を未読にする (search1, search2 両方に含まれる)
676             this.tabinfo.SetReadAllTab(200L, read: false);
677             Assert.Equal(1, tab1.UnreadCount);
678             Assert.Equal(1, tab2.UnreadCount);
679
680             // StatusId: 100 を未読にする (search1 のみに含まれる)
681             this.tabinfo.SetReadAllTab(100L, read: false);
682             Assert.Equal(2, tab1.UnreadCount);
683             Assert.Equal(1, tab2.UnreadCount);
684         }
685
686         [Fact]
687         public void SetReadHomeTab_Test()
688         {
689             var homeTab = this.tabinfo.Tabs["Recent"];
690
691             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
692             homeTab.UnreadManage = true;
693             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
694             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
695             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
696
697             this.tabinfo.DistributePosts();
698             this.tabinfo.SubmitUpdate();
699
700             // この時点でのHomeタブの未読件数
701             Assert.Equal(3, homeTab.UnreadCount);
702
703             // Recent タブのツイートをすべて未読にする
704             this.tabinfo.SetReadHomeTab();
705             Assert.Equal(0, homeTab.UnreadCount);
706         }
707
708         [Fact]
709         public void SetReadHomeTab_ContainsReplyTest()
710         {
711             var homeTab = this.tabinfo.Tabs["Recent"];
712
713             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
714             // StatusId: 150 は未読だがリプライ属性が付いている
715             homeTab.UnreadManage = true;
716             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
717             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false, IsReply = true });
718             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
719
720             this.tabinfo.DistributePosts();
721             this.tabinfo.SubmitUpdate();
722
723             // この時点でのHomeタブの未読件数
724             Assert.Equal(3, homeTab.UnreadCount);
725
726             // Recent タブのツイートをすべて未読にする
727             this.tabinfo.SetReadHomeTab();
728
729             // リプライである StatusId: 150 を除いてすべて未読になっている
730             Assert.Equal(1, homeTab.UnreadCount);
731             Assert.Equal(150L, homeTab.NextUnreadId);
732         }
733
734         [Fact]
735         public void SetReadHomeTab_ContainsFilterHitTest()
736         {
737             var homeTab = this.tabinfo.Tabs["Recent"];
738
739             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
740             homeTab.UnreadManage = true;
741             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
742             this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
743             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
744
745             // StatusId: 150 だけ FilterTab の振り分けルールにヒットする (PostClass.FilterHit が true になる)
746             var filterTab = new FilterTabModel("FilterTab");
747             filterTab.AddFilter(TestPostFilterRule.Create(x =>
748                 x.StatusId == 150L ? MyCommon.HITRESULT.Copy : MyCommon.HITRESULT.None));
749             this.tabinfo.AddTab(filterTab);
750
751             this.tabinfo.DistributePosts();
752             this.tabinfo.SubmitUpdate();
753
754             // この時点でのHomeタブの未読件数
755             Assert.Equal(3, homeTab.UnreadCount);
756
757             // Recent タブのツイートをすべて未読にする
758             this.tabinfo.SetReadHomeTab();
759
760             // FilterHit が true である StatusId: 150 を除いてすべて未読になっている
761             Assert.Equal(1, homeTab.UnreadCount);
762             Assert.Equal(150L, homeTab.NextUnreadId);
763         }
764
765         [Fact]
766         public void SubmitUpdate_RemoveSubmit_Test()
767         {
768             var homeTab = this.tabinfo.HomeTab;
769
770             this.tabinfo.AddPost(new PostClass { StatusId = 100L });
771             this.tabinfo.DistributePosts();
772             this.tabinfo.SubmitUpdate();
773
774             Assert.Equal(1, homeTab.AllCount);
775
776             this.tabinfo.RemovePostFromAllTabs(100L, setIsDeleted: true);
777
778             // この時点ではまだ削除されない
779             Assert.Equal(1, homeTab.AllCount);
780
781             this.tabinfo.SubmitUpdate(out _, out _, out _, out var isDeletePost);
782
783             Assert.True(isDeletePost);
784             Assert.Equal(0, homeTab.AllCount);
785             Assert.False(this.tabinfo.Posts.ContainsKey(100L));
786         }
787
788         [Fact]
789         public void SubmitUpdate_RemoveSubmit_NotOrphaned_Test()
790         {
791             var homeTab = this.tabinfo.HomeTab;
792             var favTab = this.tabinfo.FavoriteTab;
793
794             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsFav = true });
795             this.tabinfo.DistributePosts();
796             this.tabinfo.SubmitUpdate();
797
798             Assert.Equal(1, homeTab.AllCount);
799             Assert.Equal(1, favTab.AllCount);
800
801             // favTab のみ発言を除去 (homeTab には残ったまま)
802             favTab.EnqueueRemovePost(100L, setIsDeleted: false);
803
804             // この時点ではまだ削除されない
805             Assert.Equal(1, homeTab.AllCount);
806             Assert.Equal(1, favTab.AllCount);
807
808             this.tabinfo.SubmitUpdate(out _, out _, out _, out var isDeletePost);
809
810             Assert.True(isDeletePost);
811             Assert.Equal(1, homeTab.AllCount);
812             Assert.Equal(0, favTab.AllCount);
813
814             // homeTab には発言が残っているので Posts からは削除されない
815             Assert.True(this.tabinfo.Posts.ContainsKey(100L));
816         }
817
818         [Fact]
819         public void SubmitUpdate_NotifyPriorityTest()
820         {
821             var homeTab = this.tabinfo.HomeTab;
822             homeTab.UnreadManage = true;
823             homeTab.Notify = true;
824             homeTab.SoundFile = "home.wav";
825
826             var replyTab = this.tabinfo.MentionTab;
827             replyTab.UnreadManage = true;
828             replyTab.Notify = true;
829             replyTab.SoundFile = "reply.wav";
830
831             var dmTab = this.tabinfo.DirectMessageTab;
832             dmTab.UnreadManage = true;
833             dmTab.Notify = true;
834             dmTab.SoundFile = "dm.wav";
835
836             // 通常ツイート
837             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
838
839             // リプライ
840             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
841
842             // DM
843             dmTab.AddPostQueue(new PostClass { StatusId = 300L, IsDm = true, IsRead = false });
844
845             this.tabinfo.DistributePosts();
846
847             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts, out _, out _);
848
849             // DM が最も優先度が高いため DM の通知音が再生される
850             Assert.Equal("dm.wav", soundFile);
851
852             // 通知対象のツイートは 3 件
853             Assert.Equal(3, notifyPosts.Length);
854         }
855
856         [Fact]
857         public void SubmitUpdate_IgnoreEmptySoundPath_Test()
858         {
859             var homeTab = this.tabinfo.HomeTab;
860             homeTab.UnreadManage = true;
861             homeTab.Notify = true;
862             homeTab.SoundFile = "home.wav";
863
864             var replyTab = this.tabinfo.MentionTab;
865             replyTab.UnreadManage = true;
866             replyTab.Notify = true;
867             replyTab.SoundFile = "";
868
869             // 通常ツイート
870             this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
871
872             // リプライ
873             this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
874
875             this.tabinfo.DistributePosts();
876
877             this.tabinfo.SubmitUpdate(out var soundFile, out var notifyPosts, out _, out _);
878
879             // リプライの方が通知音の優先度が高いが、replyTab.SoundFile が空文字列なので次点の Recent の通知音を鳴らす
880             Assert.Equal("home.wav", soundFile);
881
882             // 通知対象のツイートは 2 件
883             Assert.Equal(2, notifyPosts.Length);
884         }
885
886         [Fact]
887         public void FilterAll_CopyFilterTest()
888         {
889             var homeTab = this.tabinfo.HomeTab;
890
891             var myTab1 = new FilterTabModel("MyTab1");
892             this.tabinfo.AddTab(myTab1);
893
894             var filter = new PostFilterRule
895             {
896                 FilterName = "aaa",
897
898                 // コピーのみ
899                 MoveMatches = false,
900                 MarkMatches = false,
901             };
902             myTab1.AddFilter(filter);
903             myTab1.FilterModified = false;
904
905             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
906             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
907             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
908             this.tabinfo.DistributePosts();
909             this.tabinfo.SubmitUpdate();
910
911             // この時点での振り分け状態
912             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
913             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
914
915             // フィルタを変更する
916             filter.FilterName = "bbb";
917
918             // フィルタの変更を反映
919             this.tabinfo.FilterAll();
920             this.tabinfo.DistributePosts();
921             this.tabinfo.SubmitUpdate();
922
923             // 期待する動作:
924             //   [statusId: 100] は MyTab1 から取り除かれる
925             //   [statusId: 200] は Recent から MyTab1 にコピーされる
926
927             // 変更後の振り分け状態
928             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
929             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
930         }
931
932         [Fact]
933         public void FilterAll_CopyAndMarkFilterTest()
934         {
935             var homeTab = this.tabinfo.HomeTab;
936
937             var myTab1 = new FilterTabModel("MyTab1");
938             this.tabinfo.AddTab(myTab1);
939
940             var filter = new PostFilterRule
941             {
942                 FilterName = "aaa",
943
944                 // コピー+マーク
945                 MoveMatches = false,
946                 MarkMatches = true,
947             };
948             myTab1.AddFilter(filter);
949             myTab1.FilterModified = false;
950
951             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
952             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
953             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
954             this.tabinfo.DistributePosts();
955             this.tabinfo.SubmitUpdate();
956
957             // この時点での振り分け状態
958             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
959             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
960
961             // フィルタを変更する
962             filter.FilterName = "bbb";
963
964             // フィルタの変更を反映
965             this.tabinfo.FilterAll();
966             this.tabinfo.DistributePosts();
967             this.tabinfo.SubmitUpdate();
968
969             // 期待する動作:
970             //   [statusId: 100] は MyTab1 から取り除かれる
971             //   [statusId: 200] は Recent から MyTab1 にコピーされ、マークが付与される
972
973             // 変更後の振り分け状態
974             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
975             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
976
977             // [statusId: 200] は IsMark が true の状態になる
978             Assert.True(this.tabinfo[200L]!.IsMark);
979         }
980
981         [Fact]
982         public void FilterAll_MoveFilterTest()
983         {
984             var homeTab = this.tabinfo.HomeTab;
985
986             var myTab1 = new FilterTabModel("MyTab1");
987             this.tabinfo.AddTab(myTab1);
988
989             var filter = new PostFilterRule
990             {
991                 FilterName = "aaa",
992
993                 // マッチしたら移動
994                 MoveMatches = true,
995             };
996             myTab1.AddFilter(filter);
997             myTab1.FilterModified = false;
998
999             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
1000             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
1001             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
1002             this.tabinfo.DistributePosts();
1003             this.tabinfo.SubmitUpdate();
1004
1005             // この時点での振り分け状態
1006             Assert.Equal(new[] { 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
1007             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
1008
1009             // フィルタを変更する
1010             filter.FilterName = "bbb";
1011
1012             // フィルタの変更を反映
1013             this.tabinfo.FilterAll();
1014             this.tabinfo.DistributePosts();
1015             this.tabinfo.SubmitUpdate();
1016
1017             // 期待する動作:
1018             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
1019             //   [statusId: 200] は Recent から MyTab1 に移動される
1020
1021             // 変更後の振り分け状態
1022             Assert.Equal(new[] { 100L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
1023             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
1024         }
1025
1026         [Fact]
1027         public void FilterAll_MoveFilterTest2()
1028         {
1029             var homeTab = this.tabinfo.HomeTab;
1030
1031             var myTab1 = new FilterTabModel("MyTab1");
1032             var myTab2 = new FilterTabModel("MyTab2");
1033             this.tabinfo.AddTab(myTab1);
1034             this.tabinfo.AddTab(myTab2);
1035
1036             var filter1 = new PostFilterRule
1037             {
1038                 FilterName = "aaa",
1039
1040                 // マッチしたら移動
1041                 MoveMatches = true,
1042             };
1043             myTab1.AddFilter(filter1);
1044             myTab1.FilterModified = false;
1045
1046             var filter2 = new PostFilterRule
1047             {
1048                 FilterName = "bbb",
1049
1050                 // マッチしたら移動
1051                 MoveMatches = true,
1052             };
1053             myTab2.AddFilter(filter2);
1054             myTab2.FilterModified = false;
1055
1056             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
1057             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
1058             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
1059             this.tabinfo.DistributePosts();
1060             this.tabinfo.SubmitUpdate();
1061
1062             // この時点での振り分け状態
1063             Assert.Equal(new[] { 300L }, homeTab.StatusIds);
1064             Assert.Equal(new[] { 100L }, myTab1.StatusIds);
1065             Assert.Equal(new[] { 200L }, myTab2.StatusIds);
1066
1067             // MyTab1 のフィルタを変更する
1068             filter1.FilterName = "bbb";
1069
1070             // MyTab2 のフィルタを変更する
1071             filter2.FilterName = "ccc";
1072
1073             // フィルタの変更を反映
1074             this.tabinfo.FilterAll();
1075             this.tabinfo.DistributePosts();
1076             this.tabinfo.SubmitUpdate();
1077
1078             // 期待する動作:
1079             //   [statusId: 100] は MyTab1 から取り除かれて Recent に戻される
1080             //   [statusId: 200] は MyTab1 に移動される
1081             //   [statusId: 200] は MyTab2 から取り除かれるが MyTab1 に移動されているので Recent には戻さない
1082             //   [statusId: 300] は Recent から MyTab2 に移動される
1083
1084             // 変更後の振り分け状態
1085             Assert.Equal(new[] { 100L }, homeTab.StatusIds);
1086             Assert.Equal(new[] { 200L }, myTab1.StatusIds);
1087             Assert.Equal(new[] { 300L }, myTab2.StatusIds);
1088         }
1089
1090         [Fact]
1091         public void FilterAll_ExcludeReplyFilterTest()
1092         {
1093             var homeTab = this.tabinfo.HomeTab;
1094             var replyTab = this.tabinfo.MentionTab;
1095
1096             var filter = new PostFilterRule
1097             {
1098                 // @aaa からのリプライは Reply タブに振り分けない
1099                 ExFilterName = "aaa",
1100             };
1101             replyTab.AddFilter(filter);
1102             replyTab.FilterModified = false;
1103
1104             this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa", IsReply = true });
1105             this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb", IsReply = true });
1106             this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc", IsReply = true });
1107             this.tabinfo.DistributePosts();
1108             this.tabinfo.SubmitUpdate();
1109
1110             // この時点での振り分け状態
1111             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
1112             Assert.Equal(new[] { 200L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
1113
1114             // [statusId: 100] は IsExcludeReply が true の状態になっている
1115             Assert.True(this.tabinfo[100L]!.IsExcludeReply);
1116
1117             // Reply のフィルタを変更する
1118             filter.ExFilterName = "bbb";
1119
1120             // フィルタの変更を反映
1121             this.tabinfo.FilterAll();
1122             this.tabinfo.DistributePosts();
1123             this.tabinfo.SubmitUpdate();
1124
1125             // 期待する動作:
1126             //   [statusId: 100] は Reply にコピーされ、IsExcludeReply が false になる
1127             //   [statusId: 200] は Reply から取り除かれ、IsExcludeReply が true になる
1128
1129             // 変更後の振り分け状態
1130             Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
1131             Assert.Equal(new[] { 100L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
1132
1133             // [statusId: 100] は IsExcludeReply が false の状態になる
1134             Assert.False(this.tabinfo[100L]!.IsExcludeReply);
1135
1136             // [statusId: 200] は IsExcludeReply が true の状態になる
1137             Assert.True(this.tabinfo[200L]!.IsExcludeReply);
1138         }
1139
1140         [Fact]
1141         public void RefreshOwl_HomeTabTest()
1142         {
1143             var post = new PostClass
1144             {
1145                 StatusId = 100L,
1146                 ScreenName = "aaa",
1147                 UserId = 123L,
1148                 IsOwl = true,
1149             };
1150             this.tabinfo.AddPost(post);
1151             this.tabinfo.DistributePosts();
1152             this.tabinfo.SubmitUpdate();
1153
1154             var followerIds = new HashSet<long> { 123L };
1155             this.tabinfo.RefreshOwl(followerIds);
1156
1157             Assert.False(post.IsOwl);
1158         }
1159
1160         [Fact]
1161         public void RefreshOwl_InnerStoregeTabTest()
1162         {
1163             var tab = new PublicSearchTabModel("search");
1164             this.tabinfo.AddTab(tab);
1165
1166             var post = new PostClass
1167             {
1168                 StatusId = 100L,
1169                 ScreenName = "aaa",
1170                 UserId = 123L,
1171                 IsOwl = true,
1172             };
1173             tab.AddPostQueue(post);
1174             this.tabinfo.DistributePosts();
1175             this.tabinfo.SubmitUpdate();
1176
1177             var followerIds = new HashSet<long> { 123L };
1178             this.tabinfo.RefreshOwl(followerIds);
1179
1180             Assert.False(post.IsOwl);
1181         }
1182
1183         [Fact]
1184         public void RefreshOwl_UnfollowedTest()
1185         {
1186             var post = new PostClass
1187             {
1188                 StatusId = 100L,
1189                 ScreenName = "aaa",
1190                 UserId = 123L,
1191                 IsOwl = false,
1192             };
1193             this.tabinfo.AddPost(post);
1194             this.tabinfo.DistributePosts();
1195             this.tabinfo.SubmitUpdate();
1196
1197             var followerIds = new HashSet<long> { 456L };
1198             this.tabinfo.RefreshOwl(followerIds);
1199
1200             Assert.True(post.IsOwl);
1201         }
1202
1203         private class TestPostFilterRule : PostFilterRule
1204         {
1205             public static PostFilterRule Create(Func<PostClass, MyCommon.HITRESULT> filterDelegate)
1206             {
1207                 return new TestPostFilterRule
1208                 {
1209                     filterDelegate = filterDelegate,
1210                     IsDirty = false,
1211                 };
1212             }
1213         }
1214     }
1215 }