OSDN Git Service

Assert.Empty, Assert.Single を使用する (xUnit2013)
[opentween/open-tween.git] / OpenTween.Tests / Models / TabInformationTest.cs
index 6789042..56ee167 100644 (file)
@@ -24,6 +24,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using System.Windows.Forms;
 using Xunit;
 using Xunit.Extensions;
 
@@ -35,10 +36,10 @@ namespace OpenTween.Models
 
         public TabInformationTest()
         {
-            this.tabinfo = (TabInformations)Activator.CreateInstance(typeof(TabInformations), true);
+            this.tabinfo = this.CreateInstance();
 
             // TabInformation.GetInstance() で取得できるようにする
-            var field = typeof(TabInformations).GetField("_instance",
+            var field = typeof(TabInformations).GetField("Instance",
                 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField);
             field.SetValue(null, this.tabinfo);
 
@@ -49,6 +50,9 @@ namespace OpenTween.Models
             this.tabinfo.AddTab(new FavoritesTabModel("Favorites"));
         }
 
+        private TabInformations CreateInstance()
+            => (TabInformations)Activator.CreateInstance(typeof(TabInformations), true);
+
         [Fact]
         public void AddTab_Test()
         {
@@ -68,6 +72,232 @@ namespace OpenTween.Models
         }
 
         [Fact]
+        public void RemoveTab_InnerStorageTabTest()
+        {
+            var tab = new PublicSearchTabModel("search");
+            tab.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("100") });
+            this.tabinfo.AddTab(tab);
+            this.tabinfo.SubmitUpdate();
+
+            Assert.True(this.tabinfo.ContainsTab("search"));
+            Assert.Empty(this.tabinfo.RemovedTab);
+
+            this.tabinfo.RemoveTab("search");
+
+            Assert.False(this.tabinfo.ContainsTab("search"));
+            Assert.Single(this.tabinfo.RemovedTab);
+            Assert.Contains(tab, this.tabinfo.RemovedTab);
+        }
+
+        [Fact]
+        public void RemoveTab_FilterTab_MovedPost_OrphanedTest()
+        {
+            var filterTab = new FilterTabModel("filter");
+            filterTab.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "opentween" });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            Assert.False(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.RemoveTab("filter");
+
+            Assert.False(this.tabinfo.ContainsTab("filter"));
+            Assert.Single(this.tabinfo.RemovedTab);
+            Assert.Contains(filterTab, this.tabinfo.RemovedTab);
+
+            // 他に MoveMatches で移動している振り分けタブが存在しなければ Home タブに戻す
+            Assert.True(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void RemoveTab_FilterTab_MovedPost_NotOrphanedTest()
+        {
+            var filterTab1 = new FilterTabModel("filter1");
+            filterTab1.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab1);
+
+            var filterTab2 = new FilterTabModel("filter2");
+            filterTab2.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab2);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "opentween" });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            Assert.False(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab1.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab2.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.RemoveTab("filter1");
+
+            Assert.False(this.tabinfo.ContainsTab("filter1"));
+            Assert.Single(this.tabinfo.RemovedTab);
+            Assert.Contains(filterTab1, this.tabinfo.RemovedTab);
+
+            // 他に MoveMatches で移動している振り分けタブが存在する場合は Home タブに戻さない
+            Assert.False(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab2.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void RemoveTab_FilterTab_CopiedPost_Test()
+        {
+            var filterTab = new FilterTabModel("filter");
+            filterTab.AddFilter(new() { FilterName = "opentween", MoveMatches = false });
+            this.tabinfo.AddTab(filterTab);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "opentween" });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            Assert.True(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.RemoveTab("filter");
+
+            Assert.False(this.tabinfo.ContainsTab("filter"));
+            Assert.Single(this.tabinfo.RemovedTab);
+            Assert.Contains(filterTab, this.tabinfo.RemovedTab);
+
+            // 振り分けタブにコピーされた発言は Home タブにも存在しているため何もしない
+            Assert.True(this.tabinfo.HomeTab.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void CanUndoRemovedTab_Test()
+        {
+            var tab = new PublicSearchTabModel("tab");
+            this.tabinfo.AddTab(tab);
+            Assert.False(this.tabinfo.CanUndoRemovedTab);
+
+            this.tabinfo.RemoveTab(tab.TabName);
+            Assert.True(this.tabinfo.CanUndoRemovedTab);
+        }
+
+        [Fact]
+        public void UndoRemovedTab_Test()
+        {
+            var tab = new PublicSearchTabModel("tab");
+            this.tabinfo.AddTab(tab);
+            Assert.True(this.tabinfo.ContainsTab("tab"));
+            Assert.Empty(this.tabinfo.RemovedTab);
+
+            this.tabinfo.RemoveTab("tab");
+            Assert.False(this.tabinfo.ContainsTab("tab"));
+            Assert.Single(this.tabinfo.RemovedTab);
+
+            var restoredTab = this.tabinfo.UndoRemovedTab();
+            Assert.True(this.tabinfo.ContainsTab("tab"));
+            Assert.Same(tab, restoredTab);
+            Assert.Empty(this.tabinfo.RemovedTab);
+        }
+
+        [Fact]
+        public void UndoRemovedTab_EmptyError_Test()
+        {
+            Assert.Empty(this.tabinfo.RemovedTab);
+            Assert.Throws<TabException>(
+                () => this.tabinfo.UndoRemovedTab()
+            );
+        }
+
+        [Fact]
+        public void UndoRemovedTab_DuplicatedName_Test()
+        {
+            var tab = new PublicSearchTabModel("tab");
+            this.tabinfo.AddTab(tab);
+            Assert.Empty(this.tabinfo.RemovedTab);
+
+            this.tabinfo.RemoveTab("tab");
+            Assert.Single(this.tabinfo.RemovedTab);
+
+            this.tabinfo.RenameTab("Recent", "tab");
+            Assert.Throws<TabException>(
+                () => this.tabinfo.UndoRemovedTab()
+            );
+            Assert.Single(this.tabinfo.RemovedTab);
+        }
+
+        [Fact]
+        public void RenameTab_PositionTest()
+        {
+            var replyTab = this.tabinfo.Tabs["Reply"];
+            Assert.Equal(1, this.tabinfo.Tabs.IndexOf(replyTab));
+
+            this.tabinfo.RenameTab("Reply", "Reply12345");
+
+            Assert.Equal("Reply12345", replyTab.TabName);
+            Assert.Equal(1, this.tabinfo.Tabs.IndexOf(replyTab));
+        }
+
+        [Fact]
+        public void RenameTab_SelectedTabTest()
+        {
+            var replyTab = this.tabinfo.Tabs["Reply"];
+
+            this.tabinfo.SelectTab("Reply");
+            this.tabinfo.RenameTab("Reply", "Reply12345");
+
+            Assert.Equal("Reply12345", replyTab.TabName);
+            Assert.Equal("Reply12345", this.tabinfo.SelectedTabName);
+            Assert.Equal(replyTab, this.tabinfo.SelectedTab);
+        }
+
+        [Fact]
+        public void MoveTab_MoveToStart_Test()
+        {
+            Assert.Equal(0, this.tabinfo.Tabs.IndexOf("Recent"));
+            Assert.Equal(1, this.tabinfo.Tabs.IndexOf("Reply"));
+
+            this.tabinfo.MoveTab(0, this.tabinfo.MentionTab);
+
+            Assert.Equal(0, this.tabinfo.Tabs.IndexOf("Reply"));
+            Assert.Equal(1, this.tabinfo.Tabs.IndexOf("Recent"));
+        }
+
+        [Fact]
+        public void MoveTab_MoveToEnd_Test()
+        {
+            Assert.Equal(4, this.tabinfo.Tabs.Count);
+            Assert.Equal(2, this.tabinfo.Tabs.IndexOf("DM"));
+            Assert.Equal(3, this.tabinfo.Tabs.IndexOf("Favorites"));
+
+            this.tabinfo.MoveTab(3, this.tabinfo.DirectMessageTab);
+
+            Assert.Equal(2, this.tabinfo.Tabs.IndexOf("Favorites"));
+            Assert.Equal(3, this.tabinfo.Tabs.IndexOf("DM"));
+        }
+
+        [Fact]
+        public void MoveTab_OutOfRangeError_Test()
+        {
+            Assert.Equal(4, this.tabinfo.Tabs.Count);
+            Assert.Throws<ArgumentOutOfRangeException>(
+                () => this.tabinfo.MoveTab(-1, this.tabinfo.HomeTab)
+            );
+            Assert.Throws<ArgumentOutOfRangeException>(
+                () => this.tabinfo.MoveTab(4, this.tabinfo.HomeTab)
+            );
+        }
+
+        [Theory]
+        [InlineData("Reply", true)]
+        [InlineData("UNKNOWN NAME", false)]
+        public void ContainsTab_TabName_Test(string tabName, bool expected)
+            => Assert.Equal(expected, this.tabinfo.ContainsTab(tabName));
+
+        [Fact]
+        public void ContainsTab_TabInstance_Test()
+        {
+            Assert.True(this.tabinfo.ContainsTab(this.tabinfo.HomeTab));
+            Assert.False(this.tabinfo.ContainsTab(new PublicSearchTabModel("tab")));
+        }
+
+        [Fact]
         public void SelectTab_Test()
         {
             this.tabinfo.SelectTab("Reply");
@@ -81,6 +311,149 @@ namespace OpenTween.Models
             => Assert.Throws<ArgumentException>(() => this.tabinfo.SelectTab("INVALID"));
 
         [Fact]
+        public void LoadTabsFromSettings_Test()
+        {
+            var settingTabs = new SettingTabs
+            {
+                Tabs =
+                {
+                    new()
+                    {
+                        TabName = "hoge",
+                        TabType = MyCommon.TabUsageType.PublicSearch,
+                        SearchWords = "aaa",
+                    },
+                },
+            };
+            var tabinfo = this.CreateInstance();
+            tabinfo.LoadTabsFromSettings(settingTabs);
+            Assert.Single(tabinfo.Tabs);
+
+            var tab = (PublicSearchTabModel)tabinfo.Tabs["hoge"];
+            Assert.Equal("aaa", tab.SearchWords);
+        }
+
+        [Fact]
+        public void LoadTabsFromSettings_DuplicateTabNameTest()
+        {
+            var settingTabs = new SettingTabs
+            {
+                Tabs =
+                {
+                    new()
+                    {
+                        TabName = "hoge",
+                        TabType = MyCommon.TabUsageType.PublicSearch,
+                        SearchWords = "aaa",
+                    },
+                    new()
+                    {
+                        TabName = "hoge", // 重複したタブ名
+                        TabType = MyCommon.TabUsageType.PublicSearch,
+                        SearchWords = "bbb",
+                    },
+                },
+            };
+            var tabinfo = this.CreateInstance();
+            tabinfo.LoadTabsFromSettings(settingTabs);
+            Assert.Equal(2, tabinfo.Tabs.Count);
+
+            var tab1 = (PublicSearchTabModel)tabinfo.Tabs["hoge"];
+            Assert.Equal("aaa", tab1.SearchWords);
+
+            var tab2 = (PublicSearchTabModel)tabinfo.Tabs["hoge2"];
+            Assert.Equal("bbb", tab2.SearchWords);
+        }
+
+        [Theory]
+        [InlineData(MyCommon.TabUsageType.Home, typeof(HomeTabModel))]
+        [InlineData(MyCommon.TabUsageType.Mentions, typeof(MentionsTabModel))]
+        [InlineData(MyCommon.TabUsageType.DirectMessage, typeof(DirectMessagesTabModel))]
+        [InlineData(MyCommon.TabUsageType.Favorites, typeof(FavoritesTabModel))]
+        [InlineData(MyCommon.TabUsageType.UserDefined, typeof(FilterTabModel))]
+        [InlineData(MyCommon.TabUsageType.UserTimeline, typeof(UserTimelineTabModel))]
+        [InlineData(MyCommon.TabUsageType.PublicSearch, typeof(PublicSearchTabModel))]
+        [InlineData(MyCommon.TabUsageType.Lists, typeof(ListTimelineTabModel))]
+        [InlineData(MyCommon.TabUsageType.Mute, typeof(MuteTabModel))]
+        public void CreateTabFromSettings_TabTypeTest(MyCommon.TabUsageType tabType, Type expected)
+        {
+            var tabSetting = new SettingTabs.SettingTabItem
+            {
+                TabName = "tab",
+                TabType = tabType,
+            };
+            var tabinfo = this.CreateInstance();
+            var tab = tabinfo.CreateTabFromSettings(tabSetting);
+            Assert.IsType(expected, tab);
+        }
+
+        [Fact]
+        public void CreateTabFromSettings_FilterTabTest()
+        {
+            var tabSetting = new SettingTabs.SettingTabItem
+            {
+                TabName = "tab",
+                TabType = MyCommon.TabUsageType.UserDefined,
+                FilterArray = new PostFilterRule[]
+                {
+                    new() { FilterName = "foo" },
+                },
+            };
+            var tabinfo = this.CreateInstance();
+            var tab = tabinfo.CreateTabFromSettings(tabSetting);
+            Assert.IsType<FilterTabModel>(tab);
+
+            var filterTab = (FilterTabModel)tab!;
+            Assert.Equal("foo", filterTab.FilterArray.First().FilterName);
+        }
+
+        [Fact]
+        public void CreateTabFromSettings_PublicSearchTabTest()
+        {
+            var tabSetting = new SettingTabs.SettingTabItem
+            {
+                TabName = "tab",
+                TabType = MyCommon.TabUsageType.PublicSearch,
+                SearchWords = "foo",
+                SearchLang = "ja",
+            };
+            var tabinfo = this.CreateInstance();
+            var tab = tabinfo.CreateTabFromSettings(tabSetting);
+            Assert.IsType<PublicSearchTabModel>(tab);
+
+            var searchTab = (PublicSearchTabModel)tab!;
+            Assert.Equal("foo", searchTab.SearchWords);
+            Assert.Equal("ja", searchTab.SearchLang);
+        }
+
+        [Fact]
+        public void AddDefaultTabs_Test()
+        {
+            var tabinfo = this.CreateInstance();
+            Assert.Empty(tabinfo.Tabs);
+
+            tabinfo.AddDefaultTabs();
+
+            Assert.Equal(4, tabinfo.Tabs.Count);
+            Assert.IsType<HomeTabModel>(tabinfo.Tabs[0]);
+            Assert.IsType<MentionsTabModel>(tabinfo.Tabs[1]);
+            Assert.IsType<DirectMessagesTabModel>(tabinfo.Tabs[2]);
+            Assert.IsType<FavoritesTabModel>(tabinfo.Tabs[3]);
+
+            var homeTab = tabinfo.HomeTab;
+            Assert.False(homeTab.Notify);
+
+            var mentionsTab = tabinfo.MentionTab;
+            Assert.True(mentionsTab.Notify);
+
+            var dmTab = tabinfo.DirectMessageTab;
+            Assert.True(dmTab.Notify);
+
+            var favsTab = tabinfo.FavoriteTab;
+            Assert.False(favsTab.Notify);
+        }
+
+        [Fact]
         public void MakeTabName_Test()
         {
             var baseTabName = "NewTab";
@@ -111,6 +484,50 @@ namespace OpenTween.Models
         }
 
         [Fact]
+        public void SetSortMode_Test()
+        {
+            this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.HomeTab.SortMode);
+            Assert.Equal(SortOrder.Descending, this.tabinfo.HomeTab.SortOrder);
+
+            this.tabinfo.SetSortMode(ComparerMode.Source, SortOrder.Ascending);
+            Assert.Equal(ComparerMode.Source, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
+            Assert.Equal(ComparerMode.Source, this.tabinfo.HomeTab.SortMode);
+            Assert.Equal(SortOrder.Ascending, this.tabinfo.HomeTab.SortOrder);
+        }
+
+        [Fact]
+        public void ToggleSortOrder_SameMode_Test()
+        {
+            this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
+
+            this.tabinfo.ToggleSortOrder(ComparerMode.Id);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
+
+            this.tabinfo.ToggleSortOrder(ComparerMode.Id);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
+        }
+
+        [Fact]
+        public void ToggleSortOrder_OtherMode_Test()
+        {
+            this.tabinfo.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+            Assert.Equal(ComparerMode.Id, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Descending, this.tabinfo.SortOrder);
+
+            this.tabinfo.ToggleSortOrder(ComparerMode.Source);
+            Assert.Equal(ComparerMode.Source, this.tabinfo.SortMode);
+            Assert.Equal(SortOrder.Ascending, this.tabinfo.SortOrder);
+        }
+
+        [Fact]
         public void IsMuted_Test()
         {
             this.tabinfo.MuteUserIds = new HashSet<long> { 12345L };
@@ -250,15 +667,15 @@ namespace OpenTween.Models
 
             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて未読)
             tab1.UnreadManage = true;
-            tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = false });
-            tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
-            tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = false });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = false });
 
             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて未読)
             tab2.UnreadManage = true;
-            tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = false });
-            tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = false });
-            tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = false });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = false });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = false });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("250"), IsRead = false });
 
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
@@ -270,12 +687,12 @@ namespace OpenTween.Models
             // ... ここまで長い前置き
 
             // StatusId: 200 を既読にする (search1, search2 両方に含まれる)
-            this.tabinfo.SetReadAllTab(200L, read: true);
+            this.tabinfo.SetReadAllTab(new TwitterStatusId("200"), read: true);
             Assert.Equal(2, tab1.UnreadCount);
             Assert.Equal(2, tab2.UnreadCount);
 
             // StatusId: 100 を既読にする (search1 のみに含まれる)
-            this.tabinfo.SetReadAllTab(100L, read: true);
+            this.tabinfo.SetReadAllTab(new TwitterStatusId("100"), read: true);
             Assert.Equal(1, tab1.UnreadCount);
             Assert.Equal(2, tab2.UnreadCount);
         }
@@ -291,15 +708,15 @@ namespace OpenTween.Models
 
             // search1 に追加するツイート (StatusId: 100, 150, 200; すべて既読)
             tab1.UnreadManage = true;
-            tab1.AddPostQueue(new PostClass { StatusId = 100L, IsRead = true });
-            tab1.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
-            tab1.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = true });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = true });
+            tab1.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = true });
 
             // search2 に追加するツイート (StatusId: 150, 200, 250; すべて既読)
             tab2.UnreadManage = true;
-            tab2.AddPostQueue(new PostClass { StatusId = 150L, IsRead = true });
-            tab2.AddPostQueue(new PostClass { StatusId = 200L, IsRead = true });
-            tab2.AddPostQueue(new PostClass { StatusId = 250L, IsRead = true });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = true });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = true });
+            tab2.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("250"), IsRead = true });
 
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
@@ -311,12 +728,12 @@ namespace OpenTween.Models
             // ... ここまで長い前置き
 
             // StatusId: 200 を未読にする (search1, search2 両方に含まれる)
-            this.tabinfo.SetReadAllTab(200L, read: false);
+            this.tabinfo.SetReadAllTab(new TwitterStatusId("200"), read: false);
             Assert.Equal(1, tab1.UnreadCount);
             Assert.Equal(1, tab2.UnreadCount);
 
             // StatusId: 100 を未読にする (search1 のみに含まれる)
-            this.tabinfo.SetReadAllTab(100L, read: false);
+            this.tabinfo.SetReadAllTab(new TwitterStatusId("100"), read: false);
             Assert.Equal(2, tab1.UnreadCount);
             Assert.Equal(1, tab2.UnreadCount);
         }
@@ -328,9 +745,9 @@ namespace OpenTween.Models
 
             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
             homeTab.UnreadManage = true;
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
-            this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = false });
 
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
@@ -351,9 +768,9 @@ namespace OpenTween.Models
             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
             // StatusId: 150 は未読だがリプライ属性が付いている
             homeTab.UnreadManage = true;
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
-            this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false, IsReply = true });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = false, IsReply = true });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = false });
 
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
@@ -366,7 +783,7 @@ namespace OpenTween.Models
 
             // リプライである StatusId: 150 を除いてすべて未読になっている
             Assert.Equal(1, homeTab.UnreadCount);
-            Assert.Equal(150L, homeTab.NextUnreadId);
+            Assert.Equal(new TwitterStatusId("150"), homeTab.NextUnreadId);
         }
 
         [Fact]
@@ -376,14 +793,14 @@ namespace OpenTween.Models
 
             // Recent に追加するツイート (StatusId: 100, 150, 200; すべて未読)
             homeTab.UnreadManage = true;
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
-            this.tabinfo.AddPost(new PostClass { StatusId = 150L, IsRead = false });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("150"), IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), IsRead = false });
 
             // StatusId: 150 だけ FilterTab の振り分けルールにヒットする (PostClass.FilterHit が true になる)
             var filterTab = new FilterTabModel("FilterTab");
             filterTab.AddFilter(TestPostFilterRule.Create(x =>
-                x.StatusId == 150L ? MyCommon.HITRESULT.Copy : MyCommon.HITRESULT.None));
+                x.StatusId == new TwitterStatusId("150") ? MyCommon.HITRESULT.Copy : MyCommon.HITRESULT.None));
             this.tabinfo.AddTab(filterTab);
 
             this.tabinfo.DistributePosts();
@@ -397,7 +814,7 @@ namespace OpenTween.Models
 
             // FilterHit が true である StatusId: 150 を除いてすべて未読になっている
             Assert.Equal(1, homeTab.UnreadCount);
-            Assert.Equal(150L, homeTab.NextUnreadId);
+            Assert.Equal(new TwitterStatusId("150"), homeTab.NextUnreadId);
         }
 
         [Fact]
@@ -405,13 +822,13 @@ namespace OpenTween.Models
         {
             var homeTab = this.tabinfo.HomeTab;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100") });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             Assert.Equal(1, homeTab.AllCount);
 
-            this.tabinfo.RemovePostFromAllTabs(100L, setIsDeleted: true);
+            this.tabinfo.RemovePostFromAllTabs(new TwitterStatusId("100"), setIsDeleted: true);
 
             // この時点ではまだ削除されない
             Assert.Equal(1, homeTab.AllCount);
@@ -420,7 +837,7 @@ namespace OpenTween.Models
 
             Assert.True(isDeletePost);
             Assert.Equal(0, homeTab.AllCount);
-            Assert.False(this.tabinfo.Posts.ContainsKey(100L));
+            Assert.False(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
         }
 
         [Fact]
@@ -429,7 +846,7 @@ namespace OpenTween.Models
             var homeTab = this.tabinfo.HomeTab;
             var favTab = this.tabinfo.FavoriteTab;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsFav = true });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsFav = true });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
@@ -437,7 +854,7 @@ namespace OpenTween.Models
             Assert.Equal(1, favTab.AllCount);
 
             // favTab のみ発言を除去 (homeTab には残ったまま)
-            favTab.EnqueueRemovePost(100L, setIsDeleted: false);
+            favTab.EnqueueRemovePost(new TwitterStatusId("100"), setIsDeleted: false);
 
             // この時点ではまだ削除されない
             Assert.Equal(1, homeTab.AllCount);
@@ -450,7 +867,7 @@ namespace OpenTween.Models
             Assert.Equal(0, favTab.AllCount);
 
             // homeTab には発言が残っているので Posts からは削除されない
-            Assert.True(this.tabinfo.Posts.ContainsKey(100L));
+            Assert.True(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
         }
 
         [Fact]
@@ -458,24 +875,27 @@ namespace OpenTween.Models
         {
             var homeTab = this.tabinfo.HomeTab;
             homeTab.UnreadManage = true;
+            homeTab.Notify = true;
             homeTab.SoundFile = "home.wav";
 
             var replyTab = this.tabinfo.MentionTab;
             replyTab.UnreadManage = true;
+            replyTab.Notify = true;
             replyTab.SoundFile = "reply.wav";
 
             var dmTab = this.tabinfo.DirectMessageTab;
             dmTab.UnreadManage = true;
+            dmTab.Notify = true;
             dmTab.SoundFile = "dm.wav";
 
             // 通常ツイート
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
 
             // リプライ
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), IsReply = true, IsRead = false });
 
             // DM
-            dmTab.AddPostQueue(new PostClass { StatusId = 300L, IsDm = true, IsRead = false });
+            dmTab.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("300"), IsDm = true, IsRead = false });
 
             this.tabinfo.DistributePosts();
 
@@ -493,17 +913,19 @@ namespace OpenTween.Models
         {
             var homeTab = this.tabinfo.HomeTab;
             homeTab.UnreadManage = true;
+            homeTab.Notify = true;
             homeTab.SoundFile = "home.wav";
 
             var replyTab = this.tabinfo.MentionTab;
             replyTab.UnreadManage = true;
+            replyTab.Notify = true;
             replyTab.SoundFile = "";
 
             // 通常ツイート
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), IsRead = false });
 
             // リプライ
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, IsReply = true, IsRead = false });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), IsReply = true, IsRead = false });
 
             this.tabinfo.DistributePosts();
 
@@ -535,15 +957,15 @@ namespace OpenTween.Models
             myTab1.AddFilter(filter);
             myTab1.FilterModified = false;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "aaa" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), ScreenName = "bbb" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("300"), ScreenName = "ccc" });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             // この時点での振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 100L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100") }, myTab1.StatusIds);
 
             // フィルタを変更する
             filter.FilterName = "bbb";
@@ -558,8 +980,8 @@ namespace OpenTween.Models
             //   [statusId: 200] は Recent から MyTab1 にコピーされる
 
             // 変更後の振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 200L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("200") }, myTab1.StatusIds);
         }
 
         [Fact]
@@ -581,15 +1003,15 @@ namespace OpenTween.Models
             myTab1.AddFilter(filter);
             myTab1.FilterModified = false;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "aaa" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), ScreenName = "bbb" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("300"), ScreenName = "ccc" });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             // この時点での振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 100L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100") }, myTab1.StatusIds);
 
             // フィルタを変更する
             filter.FilterName = "bbb";
@@ -604,11 +1026,11 @@ namespace OpenTween.Models
             //   [statusId: 200] は Recent から MyTab1 にコピーされ、マークが付与される
 
             // 変更後の振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 200L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("200") }, myTab1.StatusIds);
 
             // [statusId: 200] は IsMark が true の状態になる
-            Assert.True(this.tabinfo[200L]!.IsMark);
+            Assert.True(this.tabinfo[new TwitterStatusId("200")]!.IsMark);
         }
 
         [Fact]
@@ -629,15 +1051,15 @@ namespace OpenTween.Models
             myTab1.AddFilter(filter);
             myTab1.FilterModified = false;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "aaa" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), ScreenName = "bbb" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("300"), ScreenName = "ccc" });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             // この時点での振り分け状態
-            Assert.Equal(new[] { 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 100L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100") }, myTab1.StatusIds);
 
             // フィルタを変更する
             filter.FilterName = "bbb";
@@ -652,8 +1074,8 @@ namespace OpenTween.Models
             //   [statusId: 200] は Recent から MyTab1 に移動される
 
             // 変更後の振り分け状態
-            Assert.Equal(new[] { 100L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 200L }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("200") }, myTab1.StatusIds);
         }
 
         [Fact]
@@ -686,16 +1108,16 @@ namespace OpenTween.Models
             myTab2.AddFilter(filter2);
             myTab2.FilterModified = false;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb" });
-            this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "aaa" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), ScreenName = "bbb" });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("300"), ScreenName = "ccc" });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             // この時点での振り分け状態
-            Assert.Equal(new[] { 300L }, homeTab.StatusIds);
-            Assert.Equal(new[] { 100L }, myTab1.StatusIds);
-            Assert.Equal(new[] { 200L }, myTab2.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("300") }, homeTab.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100") }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("200") }, myTab2.StatusIds);
 
             // MyTab1 のフィルタを変更する
             filter1.FilterName = "bbb";
@@ -715,9 +1137,9 @@ namespace OpenTween.Models
             //   [statusId: 300] は Recent から MyTab2 に移動される
 
             // 変更後の振り分け状態
-            Assert.Equal(new[] { 100L }, homeTab.StatusIds);
-            Assert.Equal(new[] { 200L }, myTab1.StatusIds);
-            Assert.Equal(new[] { 300L }, myTab2.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("100") }, homeTab.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("200") }, myTab1.StatusIds);
+            Assert.Equal(new[] { new TwitterStatusId("300") }, myTab2.StatusIds);
         }
 
         [Fact]
@@ -734,18 +1156,18 @@ namespace OpenTween.Models
             replyTab.AddFilter(filter);
             replyTab.FilterModified = false;
 
-            this.tabinfo.AddPost(new PostClass { StatusId = 100L, ScreenName = "aaa", IsReply = true });
-            this.tabinfo.AddPost(new PostClass { StatusId = 200L, ScreenName = "bbb", IsReply = true });
-            this.tabinfo.AddPost(new PostClass { StatusId = 300L, ScreenName = "ccc", IsReply = true });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "aaa", IsReply = true });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("200"), ScreenName = "bbb", IsReply = true });
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("300"), ScreenName = "ccc", IsReply = true });
             this.tabinfo.DistributePosts();
             this.tabinfo.SubmitUpdate();
 
             // この時点での振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 200L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("200"), new TwitterStatusId("300") }, replyTab.StatusIds, AnyOrderComparer<PostId>.Instance);
 
             // [statusId: 100] は IsExcludeReply が true の状態になっている
-            Assert.True(this.tabinfo[100L]!.IsExcludeReply);
+            Assert.True(this.tabinfo[new TwitterStatusId("100")]!.IsExcludeReply);
 
             // Reply のフィルタを変更する
             filter.ExFilterName = "bbb";
@@ -760,23 +1182,231 @@ namespace OpenTween.Models
             //   [statusId: 200] は Reply から取り除かれ、IsExcludeReply が true になる
 
             // 変更後の振り分け状態
-            Assert.Equal(new[] { 100L, 200L, 300L }, homeTab.StatusIds, AnyOrderComparer<long>.Instance);
-            Assert.Equal(new[] { 100L, 300L }, replyTab.StatusIds, AnyOrderComparer<long>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("200"), new TwitterStatusId("300") }, homeTab.StatusIds, AnyOrderComparer<PostId>.Instance);
+            Assert.Equal(new[] { new TwitterStatusId("100"), new TwitterStatusId("300") }, replyTab.StatusIds, AnyOrderComparer<PostId>.Instance);
 
             // [statusId: 100] は IsExcludeReply が false の状態になる
-            Assert.False(this.tabinfo[100L]!.IsExcludeReply);
+            Assert.False(this.tabinfo[new TwitterStatusId("100")]!.IsExcludeReply);
 
             // [statusId: 200] は IsExcludeReply が true の状態になる
-            Assert.True(this.tabinfo[200L]!.IsExcludeReply);
+            Assert.True(this.tabinfo[new TwitterStatusId("200")]!.IsExcludeReply);
         }
 
-        class TestPostFilterRule : PostFilterRule
+        [Fact]
+        public void ClearTabIds_InnerStorageTabTest()
+        {
+            var tab = new PublicSearchTabModel("search");
+            tab.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("100") });
+            this.tabinfo.AddTab(tab);
+            this.tabinfo.SubmitUpdate();
+
+            Assert.True(tab.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.ClearTabIds("search");
+            Assert.False(tab.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void ClearTabIds_FilterTab_MovedPost_OrphanedTest()
+        {
+            var filterTab = new FilterTabModel("filter");
+            filterTab.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "opentween" });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            Assert.True(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
+            Assert.True(filterTab.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.ClearTabIds("filter");
+            Assert.False(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
+            Assert.False(filterTab.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void ClearTabIds_FilterTab_MovedPost_NotOrphanedTest()
+        {
+            var filterTab1 = new FilterTabModel("filter1");
+            filterTab1.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab1);
+
+            var filterTab2 = new FilterTabModel("filter2");
+            filterTab2.AddFilter(new() { FilterName = "opentween", MoveMatches = true });
+            this.tabinfo.AddTab(filterTab2);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100"), ScreenName = "opentween" });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            Assert.True(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
+            Assert.True(filterTab1.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab2.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.ClearTabIds("filter1");
+
+            // 他に MoveMatches で移動している振り分けタブが存在する場合は TabInformations.Posts から削除しない
+            Assert.True(this.tabinfo.ContainsKey(new TwitterStatusId("100")));
+            Assert.False(filterTab1.Contains(new TwitterStatusId("100")));
+            Assert.True(filterTab2.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void ClearTabIds_NotAffectToOtherTabs_Test()
+        {
+            var otherTab = new PublicSearchTabModel("search");
+            otherTab.AddPostQueue(new PostClass { StatusId = new TwitterStatusId("100") });
+            this.tabinfo.AddTab(otherTab);
+
+            this.tabinfo.AddPost(new PostClass { StatusId = new TwitterStatusId("100") });
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            // Recent, search のタブに status_id = 100 の発言が存在する状態
+            Assert.True(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
+            Assert.True(otherTab.Contains(new TwitterStatusId("100")));
+
+            this.tabinfo.ClearTabIds("Recent");
+            Assert.False(this.tabinfo.Posts.ContainsKey(new TwitterStatusId("100")));
+            Assert.True(otherTab.Contains(new TwitterStatusId("100")));
+        }
+
+        [Fact]
+        public void RefreshOwl_HomeTabTest()
+        {
+            var post = new PostClass
+            {
+                StatusId = new TwitterStatusId("100"),
+                ScreenName = "aaa",
+                UserId = 123L,
+                IsOwl = true,
+            };
+            this.tabinfo.AddPost(post);
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            var followerIds = new HashSet<long> { 123L };
+            this.tabinfo.RefreshOwl(followerIds);
+
+            Assert.False(post.IsOwl);
+        }
+
+        [Fact]
+        public void RefreshOwl_InnerStoregeTabTest()
+        {
+            var tab = new PublicSearchTabModel("search");
+            this.tabinfo.AddTab(tab);
+
+            var post = new PostClass
+            {
+                StatusId = new TwitterStatusId("100"),
+                ScreenName = "aaa",
+                UserId = 123L,
+                IsOwl = true,
+            };
+            tab.AddPostQueue(post);
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            var followerIds = new HashSet<long> { 123L };
+            this.tabinfo.RefreshOwl(followerIds);
+
+            Assert.False(post.IsOwl);
+        }
+
+        [Fact]
+        public void RefreshOwl_UnfollowedTest()
+        {
+            var post = new PostClass
+            {
+                StatusId = new TwitterStatusId("100"),
+                ScreenName = "aaa",
+                UserId = 123L,
+                IsOwl = false,
+            };
+            this.tabinfo.AddPost(post);
+            this.tabinfo.DistributePosts();
+            this.tabinfo.SubmitUpdate();
+
+            var followerIds = new HashSet<long> { 456L };
+            this.tabinfo.RefreshOwl(followerIds);
+
+            Assert.True(post.IsOwl);
+        }
+
+        [Fact]
+        public void GetTabByType_Generics_Test()
+        {
+            var tab = new PublicSearchTabModel("search");
+            this.tabinfo.AddTab(tab);
+            Assert.Same(tab, this.tabinfo.GetTabByType<PublicSearchTabModel>());
+        }
+
+        [Fact]
+        public void GetTabByType_Generics_NotFoundTest()
+            => Assert.Null(this.tabinfo.GetTabByType<PublicSearchTabModel>());
+
+        [Fact]
+        public void GetTabByType_Enum_Test()
+        {
+            var tab = new PublicSearchTabModel("search");
+            this.tabinfo.AddTab(tab);
+            Assert.Same(tab, this.tabinfo.GetTabByType(MyCommon.TabUsageType.PublicSearch));
+        }
+
+        [Fact]
+        public void GetTabByType_Enum_NotFoundTest()
+            => Assert.Null(this.tabinfo.GetTabByType(MyCommon.TabUsageType.PublicSearch));
+
+        [Fact]
+        public void GetTabsByType_Generics_Test()
+        {
+            var tab1 = new PublicSearchTabModel("search1");
+            var tab2 = new PublicSearchTabModel("search2");
+            this.tabinfo.AddTab(tab1);
+            this.tabinfo.AddTab(tab2);
+            Assert.Equal(new[] { tab1, tab2 }, this.tabinfo.GetTabsByType<PublicSearchTabModel>());
+        }
+
+        [Fact]
+        public void GetTabsByType_Enum_Test()
+        {
+            var tab1 = new PublicSearchTabModel("search1");
+            var tab2 = new PublicSearchTabModel("search2");
+            this.tabinfo.AddTab(tab1);
+            this.tabinfo.AddTab(tab2);
+            Assert.Equal(new[] { tab1, tab2 }, this.tabinfo.GetTabsByType(MyCommon.TabUsageType.PublicSearch));
+        }
+
+        [Fact]
+        public void GetTabsInnerStorageType_Test()
+        {
+            Assert.Equal(
+                new TabModel[] { this.tabinfo.DirectMessageTab },
+                this.tabinfo.GetTabsInnerStorageType()
+            );
+        }
+
+        [Fact]
+        public void GetTabByName_Test()
+        {
+            var tab = new PublicSearchTabModel("search");
+            this.tabinfo.AddTab(tab);
+            Assert.Same(tab, this.tabinfo.GetTabByName("search"));
+        }
+
+        [Fact]
+        public void GetTabByName_NotFoundTest()
+            => Assert.Null(this.tabinfo.GetTabByName("UNKNOWN_NAME"));
+
+        private class TestPostFilterRule : PostFilterRule
         {
             public static PostFilterRule Create(Func<PostClass, MyCommon.HITRESULT> filterDelegate)
             {
                 return new TestPostFilterRule
                 {
-                    FilterDelegate = filterDelegate,
+                    filterDelegate = filterDelegate,
                     IsDirty = false,
                 };
             }