OSDN Git Service

TimelineListViewStateに対するテストコードを追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Thu, 11 Jan 2024 07:19:49 +0000 (16:19 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Thu, 11 Jan 2024 07:39:44 +0000 (16:39 +0900)
OpenTween.Tests/TimelineListViewStateTest.cs
OpenTween/TimelineListViewState.cs

index eb4d8f4..a49d74c 100644 (file)
 // Boston, MA 02110-1301, USA.
 
 using System;
-using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Windows.Forms;
 using OpenTween.Models;
 using OpenTween.OpenTweenCustomControl;
 using Xunit;
@@ -39,5 +37,363 @@ namespace OpenTween
             var tab = new PublicSearchTabModel("hoge");
             var listViewState = new TimelineListViewState(listView, tab);
         }
+
+        private void UsingListView(int count, Action<DetailsListView, TabModel> func)
+        {
+            using var listView = new DetailsListView();
+            listView.Columns.Add("col");
+            listView.HeaderStyle = ColumnHeaderStyle.None; // 座標計算の邪魔になるため非表示にする
+
+            listView.RetrieveVirtualItem += (s, e) => e.Item = new($"text {e.ItemIndex}");
+            listView.VirtualMode = true;
+            listView.VirtualListSize = count;
+
+            using var imageList = new ImageList { ImageSize = new(1, 19) };
+            listView.SmallImageList = imageList; // Item の高さは 20px
+            listView.ClientSize = new(100, 100); // 高さは 5 行分
+            listView.CreateControl();
+
+            var tab = new PublicSearchTabModel("hoge");
+            tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+
+            var firstDateTime = new DateTimeUtc(2024, 1, 1, 0, 0, 0);
+            foreach (var i in MyCommon.CountUp(0, count - 1))
+            {
+                var post = new PostClass
+                {
+                    StatusId = new TwitterStatusId(i.ToString()),
+                    CreatedAtForSorting = firstDateTime + TimeSpan.FromSeconds(i),
+                };
+                tab.AddPostQueue(post);
+            }
+            tab.AddSubmit();
+
+            func(listView, tab);
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdAsc_Test()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.EnsureVisible(0); // 一番上にスクロール
+
+                // 投稿日時の昇順に並んでいる場合、新着投稿によってスクロール位置がズレることがないため None を返す
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.None,
+                    listViewState.GetScrollLockMode(lockScroll: false)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdAsc_BottomTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.EnsureVisible(9); // 一番下までスクロール
+
+                // 最終行が表示されている場合はスクロール位置を一番下に固定する(新着投稿を表示し続ける)
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToBottom,
+                    listViewState.GetScrollLockMode(lockScroll: false)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdAsc_BottomLockScrollTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.EnsureVisible(9); // 一番下までスクロール
+
+                // 最終行が表示されていても lockScroll が true の場合は None を返す(新着投稿にスクロールしない)
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.None,
+                    listViewState.GetScrollLockMode(lockScroll: true)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdDesc_TopTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+                listView.EnsureVisible(0); // 一番上にスクロール
+
+                // 投稿日時の降順に並んでいて 1 行目が表示されている場合はスクロール位置を一番上に固定する(新着投稿を表示し続ける)
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToTop,
+                    listViewState.GetScrollLockMode(lockScroll: false)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdDesc_TopLockScrollTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+                listView.EnsureVisible(0); // 一番上にスクロール
+
+                // 先頭行が表示されていても lockScroll が true の場合は FixedToItem を返す(新着投稿にスクロールしない)
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToItem,
+                    listViewState.GetScrollLockMode(lockScroll: true)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_IdDesc_BottomTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
+                listView.EnsureVisible(9); // 一番下にスクロール
+
+                // 先頭行が表示されていない場合は FixedToItem を返す
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToItem,
+                    listViewState.GetScrollLockMode(lockScroll: false)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetScrollLockMode_TextAsc_Test()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Data, SortOrder.Ascending);
+                listView.EnsureVisible(0); // 一番上にスクロール
+
+                // ComparerMode.Id 以外の場合は常に FixedToItem を返す
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToItem,
+                    listViewState.GetScrollLockMode(lockScroll: false)
+                );
+            });
+        }
+
+        [WinFormsFact]
+        public void GetListViewScroll_Test()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Descending); // 投稿日時の降順
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = listViewState.GetListViewScroll(lockScroll: false);
+                Assert.Equal(
+                    TimelineListViewState.ScrollLockMode.FixedToItem,
+                    scrollState.ScrollLockMode
+                );
+                Assert.Equal(new TwitterStatusId("7"), scrollState.TopItemStatusId); // 3 行目の StatusId は "7"
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewScroll_FixedToTopTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = new TimelineListViewState.ListViewScroll(
+                    ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToTop,
+                    TopItemStatusId: null
+                );
+                listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
+
+                // 一番上にスクロールされた状態になる
+                Assert.Equal(0, listView.TopItem.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewScroll_FixedToBottomTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = new TimelineListViewState.ListViewScroll(
+                    ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToBottom,
+                    TopItemStatusId: null
+                );
+                listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
+
+                // 一番下にスクロールされた状態になる(一番下に余白が生じるため null になる)
+                Assert.Null(listView.GetItemAt(0, 82)?.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewScroll_FixedToItemTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = new TimelineListViewState.ListViewScroll(
+                    ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToItem,
+                    TopItemStatusId: new TwitterStatusId("5")
+                );
+                listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
+
+                // 6 行目が一番上になる位置にスクロールされた状態になる
+                Assert.Equal(5, listView.TopItem.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewScroll_NoneTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = new TimelineListViewState.ListViewScroll(
+                    ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
+                    TopItemStatusId: new TwitterStatusId("5")
+                );
+                listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
+
+                // ScrollLockMode.None の場合は何もしない
+                Assert.Equal(2, listView.TopItem.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewScroll_ForceScrollTest()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
+                listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
+
+                var scrollState = new TimelineListViewState.ListViewScroll(
+                    ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
+                    TopItemStatusId: new TwitterStatusId("5")
+                );
+                listViewState.RestoreListViewScroll(scrollState, forceScroll: true);
+
+                // ScrollLockMode.None でも forceScroll が true の場合は FixedToItem 相当の動作になる
+                Assert.Equal(5, listView.TopItem.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void GetListViewSelection_EmptyTest()
+        {
+            this.UsingListView(count: 0, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                var selectionState = listViewState.GetListViewSelection();
+                Assert.Empty(selectionState.SelectedStatusIds);
+                Assert.Null(selectionState.SelectionMarkStatusId);
+                Assert.Null(selectionState.FocusedStatusId);
+            });
+        }
+
+        [WinFormsFact]
+        public void GetListViewSelection_Test()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                listView.SelectedIndices.Add(0);
+                listView.SelectedIndices.Add(2);
+                listView.SelectedIndices.Add(3);
+                tab.SelectPosts(new[] { 0, 2, 3 });
+                listView.SelectionMark = 1;
+                listView.FocusedItem = listView.Items[3];
+
+                var selectionState = listViewState.GetListViewSelection();
+                Assert.Equal(new TwitterStatusId[] { new("0"), new("2"), new("3") }, selectionState.SelectedStatusIds);
+                Assert.Equal(new TwitterStatusId("1"), selectionState.SelectionMarkStatusId);
+                Assert.Equal(new TwitterStatusId("3"), selectionState.FocusedStatusId);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewSelection_Test()
+        {
+            this.UsingListView(count: 10, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                var selectionState = new TimelineListViewState.ListViewSelection(
+                    SelectedStatusIds: new TwitterStatusId[] { new("1"), new("2"), new("3") },
+                    SelectionMarkStatusId: new TwitterStatusId("1"),
+                    FocusedStatusId: new TwitterStatusId("3")
+                );
+                listViewState.RestoreListViewSelection(selectionState);
+
+                Assert.Equal(new[] { 1, 2, 3 }, listView.SelectedIndices.Cast<int>());
+                Assert.Equal(1, listView.SelectionMark);
+                Assert.Equal(3, listView.FocusedItem?.Index);
+            });
+        }
+
+        [WinFormsFact]
+        public void RestoreListViewSelection_EmptyTest()
+        {
+            this.UsingListView(count: 0, (listView, tab) =>
+            {
+                var listViewState = new TimelineListViewState(listView, tab);
+
+                var selectionState = new TimelineListViewState.ListViewSelection(
+                    SelectedStatusIds: Array.Empty<PostId>(),
+                    SelectionMarkStatusId: null,
+                    FocusedStatusId: null
+                );
+                listViewState.RestoreListViewSelection(selectionState);
+
+                Assert.Empty(listView.SelectedIndices);
+                Assert.Equal(-1, listView.SelectionMark);
+                Assert.Null(listView.FocusedItem?.Index);
+            });
+        }
     }
 }
index 9878d5b..5625414 100644 (file)
@@ -78,8 +78,8 @@ namespace OpenTween
 
         public void Save(bool lockScroll)
         {
-            this.savedScrollState = this.SaveListViewScroll(lockScroll);
-            this.savedSelectionState = this.SaveListViewSelection();
+            this.savedScrollState = this.GetListViewScroll(lockScroll);
+            this.savedSelectionState = this.GetListViewSelection();
         }
 
         public void Restore(bool forceScroll = false)
@@ -97,7 +97,7 @@ namespace OpenTween
         /// <summary>
         /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
         /// </summary>
-        private ListViewScroll SaveListViewScroll(bool lockScroll)
+        internal ListViewScroll GetListViewScroll(bool lockScroll)
         {
             var lockMode = this.GetScrollLockMode(lockScroll);
             PostId? topItemStatusId = null;
@@ -117,7 +117,7 @@ namespace OpenTween
             };
         }
 
-        private ScrollLockMode GetScrollLockMode(bool lockScroll)
+        internal ScrollLockMode GetScrollLockMode(bool lockScroll)
         {
             if (this.tab.SortMode == ComparerMode.Id)
             {
@@ -159,7 +159,7 @@ namespace OpenTween
         /// <summary>
         /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
         /// </summary>
-        private ListViewSelection SaveListViewSelection()
+        internal ListViewSelection GetListViewSelection()
         {
             if (this.listView.VirtualListSize == 0)
             {
@@ -194,9 +194,9 @@ namespace OpenTween
         }
 
         /// <summary>
-        /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
+        /// <see cref="GetListViewScroll"/> によって保存されたスクロール位置を復元します
         /// </summary>
-        private void RestoreListViewScroll(ListViewScroll listScroll, bool forceScroll)
+        internal void RestoreListViewScroll(ListViewScroll listScroll, bool forceScroll)
         {
             if (this.listView.VirtualListSize == 0)
                 return;
@@ -236,9 +236,9 @@ namespace OpenTween
         }
 
         /// <summary>
-        /// <see cref="SaveListViewSelection"/> によって保存された選択状態を復元します
+        /// <see cref="GetListViewSelection"/> によって保存された選択状態を復元します
         /// </summary>
-        private void RestoreListViewSelection(ListViewSelection listSelection)
+        internal void RestoreListViewSelection(ListViewSelection listSelection)
         {
             var invalidateBounds = (Rectangle?)null;