diff --git a/Dockerfile b/Dockerfile index 7239ca2..ae4a0ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,8 @@ WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS publish COPY ./src/ ./src/ -RUN dotnet restore "/src/BirdsiteLive/BirdsiteLive.csproj" -RUN dotnet restore "/src/BSLManager/BSLManager.csproj" -RUN dotnet build "/src/BirdsiteLive/BirdsiteLive.csproj" -c Release -o /app/build - -FROM build AS publish RUN dotnet publish "/src/BirdsiteLive/BirdsiteLive.csproj" -c Release -o /app/publish RUN dotnet publish "/src/BSLManager/BSLManager.csproj" -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true -c Release -o /app/publish diff --git a/src/BSLManager/App.cs b/src/BSLManager/App.cs new file mode 100644 index 0000000..3f89294 --- /dev/null +++ b/src/BSLManager/App.cs @@ -0,0 +1,233 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Moderation.Actions; +using BSLManager.Domain; +using BSLManager.Tools; +using Terminal.Gui; + +namespace BSLManager +{ + public class App + { + private readonly IFollowersDal _followersDal; + private readonly IRemoveFollowerAction _removeFollowerAction; + + private readonly FollowersListState _state = new FollowersListState(); + + #region Ctor + public App(IFollowersDal followersDal, IRemoveFollowerAction removeFollowerAction) + { + _followersDal = followersDal; + _removeFollowerAction = removeFollowerAction; + } + #endregion + + public void Run() + { + Application.Init(); + var top = Application.Top; + + // Creates the top-level window to show + var win = new Window("BSL Manager") + { + X = 0, + Y = 1, // Leave one row for the toplevel menu + + // By using Dim.Fill(), it will automatically resize without manual intervention + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + top.Add(win); + + // Creates a menubar, the item "New" has a help menu. + var menu = new MenuBar(new MenuBarItem[] + { + new MenuBarItem("_File", new MenuItem[] + { + new MenuItem("_Quit", "", () => + { + if (Quit()) top.Running = false; + }) + }), + //new MenuBarItem ("_Edit", new MenuItem [] { + // new MenuItem ("_Copy", "", null), + // new MenuItem ("C_ut", "", null), + // new MenuItem ("_Paste", "", null) + //}) + }); + top.Add(menu); + + static bool Quit() + { + var n = MessageBox.Query(50, 7, "Quit BSL Manager", "Are you sure you want to quit?", "Yes", "No"); + return n == 0; + } + + RetrieveUserList(); + + var list = new ListView(_state.GetDisplayableList()) + { + X = 1, + Y = 3, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + list.KeyDown += _ => + { + if (_.KeyEvent.Key == Key.Enter) + { + OpenFollowerDialog(list.SelectedItem); + } + else if (_.KeyEvent.Key == Key.Delete + || _.KeyEvent.Key == Key.DeleteChar + || _.KeyEvent.Key == Key.Backspace + || _.KeyEvent.Key == Key.D) + { + OpenDeleteDialog(list.SelectedItem); + } + }; + + var listingFollowersLabel = new Label(1, 0, "Listing followers"); + var filterLabel = new Label("Filter: ") { X = 1, Y = 1 }; + var filterText = new TextField("") + { + X = Pos.Right(filterLabel), + Y = 1, + Width = 40 + }; + + filterText.KeyDown += _ => + { + var text = filterText.Text.ToString(); + if (_.KeyEvent.Key == Key.Enter && !string.IsNullOrWhiteSpace(text)) + { + _state.FilterBy(text); + ConsoleGui.RefreshUI(); + } + }; + + win.Add( + listingFollowersLabel, + filterLabel, + filterText, + list + ); + + Application.Run(); + } + + private void OpenFollowerDialog(int selectedIndex) + { + var close = new Button(3, 14, "Close"); + close.Clicked += () => Application.RequestStop(); + + var dialog = new Dialog("Info", 60, 18, close); + + var follower = _state.GetElementAt(selectedIndex); + + var name = new Label($"User: @{follower.Acct}@{follower.Host}") + { + X = 1, + Y = 1, + Width = Dim.Fill(), + Height = 1 + }; + var following = new Label($"Following Count: {follower.Followings.Count}") + { + X = 1, + Y = 3, + Width = Dim.Fill(), + Height = 1 + }; + var inbox = new Label($"Inbox: {follower.InboxRoute}") + { + X = 1, + Y = 4, + Width = Dim.Fill(), + Height = 1 + }; + var sharedInbox = new Label($"Shared Inbox: {follower.SharedInboxRoute}") + { + X = 1, + Y = 5, + Width = Dim.Fill(), + Height = 1 + }; + + dialog.Add(name); + dialog.Add(following); + dialog.Add(inbox); + dialog.Add(sharedInbox); + dialog.Add(close); + Application.Run(dialog); + } + + private void OpenDeleteDialog(int selectedIndex) + { + bool okpressed = false; + var ok = new Button(10, 14, "Yes"); + ok.Clicked += () => + { + Application.RequestStop(); + okpressed = true; + }; + + var cancel = new Button(3, 14, "No"); + cancel.Clicked += () => Application.RequestStop(); + + var dialog = new Dialog("Delete", 60, 18, cancel, ok); + + var follower = _state.GetElementAt(selectedIndex); + var name = new Label($"User: @{follower.Acct}@{follower.Host}") + { + X = 1, + Y = 1, + Width = Dim.Fill(), + Height = 1 + }; + var entry = new Label("Delete user and remove all their followings?") + { + X = 1, + Y = 3, + Width = Dim.Fill(), + Height = 1 + }; + dialog.Add(name); + dialog.Add(entry); + Application.Run(dialog); + + if (okpressed) + { + DeleteAndRemoveUser(selectedIndex); + } + } + + private void DeleteAndRemoveUser(int el) + { + Application.MainLoop.Invoke(async () => + { + var userToDelete = _state.GetElementAt(el); + await _removeFollowerAction.ProcessAsync(userToDelete); + _state.RemoveAt(el); + + ConsoleGui.RefreshUI(); + }); + } + + private void RetrieveUserList() + { + Application.MainLoop.Invoke(async () => + { + var followers = await _followersDal.GetAllFollowersAsync(); + _state.Load(followers.ToList()); + ConsoleGui.RefreshUI(); + }); + } + } +} \ No newline at end of file diff --git a/src/BSLManager/BSLManager.csproj b/src/BSLManager/BSLManager.csproj index fafc29d..52e5cde 100644 --- a/src/BSLManager/BSLManager.csproj +++ b/src/BSLManager/BSLManager.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,7 +6,23 @@ + + + + + + + + + + + + + PreserveNewest + + + diff --git a/src/BSLManager/Bootstrapper.cs b/src/BSLManager/Bootstrapper.cs new file mode 100644 index 0000000..7375cd6 --- /dev/null +++ b/src/BSLManager/Bootstrapper.cs @@ -0,0 +1,94 @@ +using System; +using System.Net.Http; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.Common.Structs; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Postgres.DataAccessLayers; +using BirdsiteLive.DAL.Postgres.Settings; +using Lamar; +using Lamar.Scanning.Conventions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace BSLManager +{ + public class Bootstrapper + { + private readonly DbSettings _dbSettings; + private readonly InstanceSettings _instanceSettings; + + #region Ctor + public Bootstrapper(DbSettings dbSettings, InstanceSettings instanceSettings) + { + _dbSettings = dbSettings; + _instanceSettings = instanceSettings; + } + #endregion + + public Container Init() + { + var container = new Container(x => + { + x.For().Use(x => _dbSettings); + + x.For().Use(x => _instanceSettings); + + if (string.Equals(_dbSettings.Type, DbTypes.Postgres, StringComparison.OrdinalIgnoreCase)) + { + var connString = $"Host={_dbSettings.Host};Username={_dbSettings.User};Password={_dbSettings.Password};Database={_dbSettings.Name}"; + var postgresSettings = new PostgresSettings + { + ConnString = connString + }; + x.For().Use(x => postgresSettings); + + x.For().Use().Singleton(); + x.For().Use().Singleton(); + x.For().Use().Singleton(); + } + else + { + throw new NotImplementedException($"{_dbSettings.Type} is not supported"); + } + + var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider(); + x.For().Use(_ => serviceProvider.GetService()); + + x.For(typeof(ILogger<>)).Use(typeof(DummyLogger<>)); + + x.Scan(_ => + { + _.Assembly("BirdsiteLive.Twitter"); + _.Assembly("BirdsiteLive.Domain"); + _.Assembly("BirdsiteLive.DAL"); + _.Assembly("BirdsiteLive.DAL.Postgres"); + _.Assembly("BirdsiteLive.Moderation"); + + _.TheCallingAssembly(); + + _.WithDefaultConventions(); + + _.LookForRegistries(); + }); + }); + return container; + } + + public class DummyLogger : ILogger + { + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + + public bool IsEnabled(LogLevel logLevel) + { + return false; + } + + public IDisposable BeginScope(TState state) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/src/BSLManager/Domain/FollowersListState.cs b/src/BSLManager/Domain/FollowersListState.cs new file mode 100644 index 0000000..f33acb8 --- /dev/null +++ b/src/BSLManager/Domain/FollowersListState.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using BirdsiteLive.DAL.Models; + +namespace BSLManager.Domain +{ + public class FollowersListState + { + private readonly List _filteredDisplayableUserList = new List(); + + private List _sourceUserList = new List(); + private List _filteredSourceUserList = new List(); + + public void Load(List followers) + { + _sourceUserList = followers.OrderByDescending(x => x.Followings.Count).ToList(); + + ResetLists(); + } + + private void ResetLists() + { + _filteredSourceUserList = _sourceUserList.ToList(); + + _filteredDisplayableUserList.Clear(); + + foreach (var follower in _sourceUserList) + { + var displayedUser = $"{GetFullHandle(follower)} ({follower.Followings.Count})"; + _filteredDisplayableUserList.Add(displayedUser); + } + } + + public List GetDisplayableList() + { + return _filteredDisplayableUserList; + } + + public void FilterBy(string pattern) + { + ResetLists(); + + if (!string.IsNullOrWhiteSpace(pattern)) + { + var elToRemove = _filteredSourceUserList + .Where(x => !GetFullHandle(x).Contains(pattern)) + .Select(x => x) + .ToList(); + + foreach (var el in elToRemove) + { + _filteredSourceUserList.Remove(el); + + var dElToRemove = _filteredDisplayableUserList.First(x => x.Contains(GetFullHandle(el))); + _filteredDisplayableUserList.Remove(dElToRemove); + } + } + } + + private string GetFullHandle(Follower follower) + { + return $"@{follower.Acct}@{follower.Host}"; + } + + public void RemoveAt(int index) + { + var displayableUser = _filteredDisplayableUserList[index]; + var sourceUser = _filteredSourceUserList[index]; + + _filteredDisplayableUserList.Remove(displayableUser); + + _filteredSourceUserList.Remove(sourceUser); + _sourceUserList.Remove(sourceUser); + } + + public Follower GetElementAt(int index) + { + return _filteredSourceUserList[index]; + } + } +} \ No newline at end of file diff --git a/src/BSLManager/Program.cs b/src/BSLManager/Program.cs index ff1a8bd..ea2569c 100644 --- a/src/BSLManager/Program.cs +++ b/src/BSLManager/Program.cs @@ -1,7 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.DAL.Contracts; +using Microsoft.Extensions.Configuration; using NStack; using Terminal.Gui; @@ -9,111 +14,22 @@ namespace BSLManager { class Program { - static void Main(string[] args) + static async Task Main(string[] args) { Console.OutputEncoding = Encoding.Default; - Application.Init(); - var top = Application.Top; + var builder = new ConfigurationBuilder() + .AddEnvironmentVariables(); + var configuration = builder.Build(); - // Creates the top-level window to show - var win = new Window("BSL Manager") - { - X = 0, - Y = 1, // Leave one row for the toplevel menu + var dbSettings = configuration.GetSection("Db").Get(); + var instanceSettings = configuration.GetSection("Instance").Get(); - // By using Dim.Fill(), it will automatically resize without manual intervention - Width = Dim.Fill(), - Height = Dim.Fill() - }; + var bootstrapper = new Bootstrapper(dbSettings, instanceSettings); + var container = bootstrapper.Init(); - top.Add(win); - - // Creates a menubar, the item "New" has a help menu. - var menu = new MenuBar(new MenuBarItem[] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; }) - }), - //new MenuBarItem ("_Edit", new MenuItem [] { - // new MenuItem ("_Copy", "", null), - // new MenuItem ("C_ut", "", null), - // new MenuItem ("_Paste", "", null) - //}) - }); - top.Add(menu); - - static bool Quit() - { - var n = MessageBox.Query(50, 7, "Quit BSL Manager", "Are you sure you want to quit?", "Yes", "No"); - return n == 0; - } - - var listData = new List(); - for (var i = 0; i < 100; i++) - { - listData.Add($"@User{i}@Instance.tld {i*3}"); - } - - var list = new ListView(listData) - { - X = 1, - Y = 2, - Width = Dim.Fill(), - Height = Dim.Fill() - }; - - list.KeyDown += _ => - { - if (_.KeyEvent.Key == Key.Enter) - { - var el = list.SelectedItem; - - bool okpressed = false; - var ok = new Button(10, 14, "Yes"); - ok.Clicked += () => - { - Application.RequestStop(); - okpressed = true; - }; - - var cancel = new Button(3, 14, "No"); - cancel.Clicked += () => Application.RequestStop(); - - var dialog = new Dialog("Delete", 60, 18, cancel, ok); - - var name = new Label($"User: {listData[el]}") - { - X = 1, - Y = 1, - Width = Dim.Fill(), - Height = 1 - }; - var entry = new Label("Delete user and remove all their followings?") - { - X = 1, - Y = 3, - Width = Dim.Fill(), - Height = 1 - }; - dialog.Add(name); - dialog.Add(entry); - Application.Run(dialog); - - if (okpressed) - { - listData.RemoveAt(el); - typeof(Application).GetMethod("TerminalResized", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); - } - } - }; - - // Add some controls, - win.Add( - new Label(1, 0, "Listing followers"), - list - ); - - Application.Run(); - } + var app = container.GetInstance(); + app.Run(); + } } } diff --git a/src/BSLManager/Tools/ConsoleGui.cs b/src/BSLManager/Tools/ConsoleGui.cs new file mode 100644 index 0000000..b6f7b6e --- /dev/null +++ b/src/BSLManager/Tools/ConsoleGui.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using Terminal.Gui; + +namespace BSLManager.Tools +{ + public static class ConsoleGui + { + public static void RefreshUI() + { + typeof(Application) + .GetMethod("TerminalResized", BindingFlags.Static | BindingFlags.NonPublic) + .Invoke(null, null); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index ba118c6..4b0cfc1 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -47,7 +47,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Moderation.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Common.Tests", "Tests\BirdsiteLive.Common.Tests\BirdsiteLive.Common.Tests.csproj", "{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSLManager", "BSLManager\BSLManager.csproj", "{4A84D351-E91B-4E58-8E20-211F0F4991D7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BSLManager", "BSLManager\BSLManager.csproj", "{4A84D351-E91B-4E58-8E20-211F0F4991D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSLManager.Tests", "Tests\BSLManager.Tests\BSLManager.Tests.csproj", "{D4457271-620E-465A-B08E-7FC63C99A2F6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -131,6 +133,10 @@ Global {4A84D351-E91B-4E58-8E20-211F0F4991D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A84D351-E91B-4E58-8E20-211F0F4991D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A84D351-E91B-4E58-8E20-211F0F4991D7}.Release|Any CPU.Build.0 = Release|Any CPU + {D4457271-620E-465A-B08E-7FC63C99A2F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4457271-620E-465A-B08E-7FC63C99A2F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4457271-620E-465A-B08E-7FC63C99A2F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4457271-620E-465A-B08E-7FC63C99A2F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -153,6 +159,7 @@ Global {4BE541AC-8A93-4FA3-98AC-956CC2D5B748} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C} {0A311BF3-4FD9-4303-940A-A3778890561C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {C69F7582-6050-44DC-BAAB-7C8F0BDA525C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} + {D4457271-620E-465A-B08E-7FC63C99A2F6} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj b/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj new file mode 100644 index 0000000..033cfe1 --- /dev/null +++ b/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/src/Tests/BSLManager.Tests/Domain/FollowersListStateTests.cs b/src/Tests/BSLManager.Tests/Domain/FollowersListStateTests.cs new file mode 100644 index 0000000..a0171a4 --- /dev/null +++ b/src/Tests/BSLManager.Tests/Domain/FollowersListStateTests.cs @@ -0,0 +1,307 @@ +using System.Collections.Generic; +using System.Linq; +using BirdsiteLive.DAL.Models; +using BSLManager.Domain; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BSLManager.Tests +{ + [TestClass] + public class FollowersListStateTests + { + [TestMethod] + public void FilterBy() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers); + + state.FilterBy("test"); + + #region Validate + Assert.AreEqual(2, state.GetDisplayableList().Count); + #endregion + } + + [TestMethod] + public void FilterBy_GetElement() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers); + + state.FilterBy("test"); + var el = state.GetElementAt(1); + + #region Validate + Assert.AreEqual(followers[1].Id, el.Id); + #endregion + } + + [TestMethod] + public void GetElement() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers); + + var el = state.GetElementAt(2); + + #region Validate + Assert.AreEqual(followers[2].Id, el.Id); + #endregion + } + + [TestMethod] + public void FilterBy_RemoveAt() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers.ToList()); + + state.FilterBy("test"); + state.RemoveAt(1); + + var list = state.GetDisplayableList(); + + #region Validate + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list[0].Contains("@test@host1")); + #endregion + } + + [TestMethod] + public void RemoveAt() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers.ToList()); + + state.RemoveAt(1); + + var list = state.GetDisplayableList(); + + #region Validate + Assert.AreEqual(3, list.Count); + Assert.IsTrue(list[0].Contains("@test@host1")); + Assert.IsFalse(list[1].Contains("@test@host2")); + #endregion + } + + [TestMethod] + public void FilterBy_ResetFilter() + { + #region Stub + var followers = new List + { + new Follower + { + Id = 0, + Acct = "test", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 1, + Acct = "test", + Host = "host2", + Followings = new List() + }, + new Follower + { + Id = 2, + Acct = "user1", + Host = "host1", + Followings = new List() + }, + new Follower + { + Id = 3, + Acct = "user2", + Host = "host1", + Followings = new List() + } + }; + #endregion + + var state = new FollowersListState(); + state.Load(followers.ToList()); + + #region Validate + state.FilterBy("data"); + var list = state.GetDisplayableList(); + Assert.AreEqual(0, list.Count); + + state.FilterBy(string.Empty); + list = state.GetDisplayableList(); + Assert.AreEqual(4, list.Count); + #endregion + } + } +}