1 // OpenTween - Client of Twitter
2 // Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
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)
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
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.
24 using System.Windows.Forms;
25 using OpenTween.Models;
26 using OpenTween.OpenTweenCustomControl;
31 public class TimelineListViewStateTest
34 public void Initialize_Test()
36 using var listView = new DetailsListView();
37 var tab = new PublicSearchTabModel("hoge");
38 var listViewState = new TimelineListViewState(listView, tab);
41 private void UsingListView(int count, Action<DetailsListView, TabModel> func)
43 using var listView = new DetailsListView();
44 listView.Columns.Add("col");
45 listView.HeaderStyle = ColumnHeaderStyle.None; // 座標計算の邪魔になるため非表示にする
47 listView.RetrieveVirtualItem += (s, e) => e.Item = new($"text {e.ItemIndex}");
48 listView.VirtualMode = true;
49 listView.VirtualListSize = count;
51 using var imageList = new ImageList { ImageSize = new(1, 19) };
52 listView.SmallImageList = imageList; // Item の高さは 20px
53 listView.ClientSize = new(100, 100); // 高さは 5 行分
54 listView.CreateControl();
56 var tab = new PublicSearchTabModel("hoge");
57 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
59 var firstDateTime = new DateTimeUtc(2024, 1, 1, 0, 0, 0);
60 foreach (var i in MyCommon.CountUp(0, count - 1))
62 var post = new PostClass
64 StatusId = new TwitterStatusId(i.ToString()),
65 CreatedAtForSorting = firstDateTime + TimeSpan.FromSeconds(i),
67 tab.AddPostQueue(post);
75 public void GetScrollLockMode_IdAsc_Test()
77 this.UsingListView(count: 10, (listView, tab) =>
79 var listViewState = new TimelineListViewState(listView, tab);
81 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
82 listView.EnsureVisible(0); // 一番上にスクロール
84 // 投稿日時の昇順に並んでいる場合、新着投稿によってスクロール位置がズレることがないため None を返す
86 TimelineListViewState.ScrollLockMode.None,
87 listViewState.GetScrollLockMode(lockScroll: false)
93 public void GetScrollLockMode_IdAsc_BottomTest()
95 this.UsingListView(count: 10, (listView, tab) =>
97 var listViewState = new TimelineListViewState(listView, tab);
99 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
100 listView.EnsureVisible(9); // 一番下までスクロール
102 // 最終行が表示されている場合はスクロール位置を一番下に固定する(新着投稿を表示し続ける)
104 TimelineListViewState.ScrollLockMode.FixedToBottom,
105 listViewState.GetScrollLockMode(lockScroll: false)
111 public void GetScrollLockMode_IdAsc_BottomLockScrollTest()
113 this.UsingListView(count: 10, (listView, tab) =>
115 var listViewState = new TimelineListViewState(listView, tab);
117 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
118 listView.EnsureVisible(9); // 一番下までスクロール
120 // 最終行が表示されていても lockScroll が true の場合は None を返す(新着投稿にスクロールしない)
122 TimelineListViewState.ScrollLockMode.None,
123 listViewState.GetScrollLockMode(lockScroll: true)
129 public void GetScrollLockMode_IdDesc_TopTest()
131 this.UsingListView(count: 10, (listView, tab) =>
133 var listViewState = new TimelineListViewState(listView, tab);
135 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
136 listView.EnsureVisible(0); // 一番上にスクロール
138 // 投稿日時の降順に並んでいて 1 行目が表示されている場合はスクロール位置を一番上に固定する(新着投稿を表示し続ける)
140 TimelineListViewState.ScrollLockMode.FixedToTop,
141 listViewState.GetScrollLockMode(lockScroll: false)
147 public void GetScrollLockMode_IdDesc_TopLockScrollTest()
149 this.UsingListView(count: 10, (listView, tab) =>
151 var listViewState = new TimelineListViewState(listView, tab);
153 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
154 listView.EnsureVisible(0); // 一番上にスクロール
156 // 先頭行が表示されていても lockScroll が true の場合は FixedToItem を返す(新着投稿にスクロールしない)
158 TimelineListViewState.ScrollLockMode.FixedToItem,
159 listViewState.GetScrollLockMode(lockScroll: true)
165 public void GetScrollLockMode_IdDesc_BottomTest()
167 this.UsingListView(count: 10, (listView, tab) =>
169 var listViewState = new TimelineListViewState(listView, tab);
171 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
172 listView.EnsureVisible(9); // 一番下にスクロール
174 // 先頭行が表示されていない場合は FixedToItem を返す
176 TimelineListViewState.ScrollLockMode.FixedToItem,
177 listViewState.GetScrollLockMode(lockScroll: false)
183 public void GetScrollLockMode_TextAsc_Test()
185 this.UsingListView(count: 10, (listView, tab) =>
187 var listViewState = new TimelineListViewState(listView, tab);
189 tab.SetSortMode(ComparerMode.Data, SortOrder.Ascending);
190 listView.EnsureVisible(0); // 一番上にスクロール
192 // ComparerMode.Id 以外の場合は常に FixedToItem を返す
194 TimelineListViewState.ScrollLockMode.FixedToItem,
195 listViewState.GetScrollLockMode(lockScroll: false)
201 public void GetListViewScroll_Test()
203 this.UsingListView(count: 10, (listView, tab) =>
205 var listViewState = new TimelineListViewState(listView, tab);
207 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending); // 投稿日時の降順
208 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
210 var scrollState = listViewState.GetListViewScroll(lockScroll: false);
212 TimelineListViewState.ScrollLockMode.FixedToItem,
213 scrollState.ScrollLockMode
215 Assert.Equal(new TwitterStatusId("7"), scrollState.TopItemStatusId); // 3 行目の StatusId は "7"
220 public void RestoreListViewScroll_FixedToTopTest()
222 this.UsingListView(count: 10, (listView, tab) =>
224 var listViewState = new TimelineListViewState(listView, tab);
226 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
227 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
229 var scrollState = new TimelineListViewState.ListViewScroll(
230 ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToTop,
231 TopItemStatusId: null
233 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
236 Assert.Equal(0, listView.TopItem.Index);
241 public void RestoreListViewScroll_FixedToBottomTest()
243 this.UsingListView(count: 10, (listView, tab) =>
245 var listViewState = new TimelineListViewState(listView, tab);
247 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
248 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
250 var scrollState = new TimelineListViewState.ListViewScroll(
251 ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToBottom,
252 TopItemStatusId: null
254 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
256 // 一番下にスクロールされた状態になる(一番下に余白が生じるため null になる)
257 Assert.Null(listView.GetItemAt(0, 82)?.Index);
262 public void RestoreListViewScroll_FixedToItemTest()
264 this.UsingListView(count: 10, (listView, tab) =>
266 var listViewState = new TimelineListViewState(listView, tab);
268 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
269 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
271 var scrollState = new TimelineListViewState.ListViewScroll(
272 ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToItem,
273 TopItemStatusId: new TwitterStatusId("5")
275 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
277 // 6 行目が一番上になる位置にスクロールされた状態になる
278 Assert.Equal(5, listView.TopItem.Index);
283 public void RestoreListViewScroll_NoneTest()
285 this.UsingListView(count: 10, (listView, tab) =>
287 var listViewState = new TimelineListViewState(listView, tab);
289 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
290 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
292 var scrollState = new TimelineListViewState.ListViewScroll(
293 ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
294 TopItemStatusId: new TwitterStatusId("5")
296 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
298 // ScrollLockMode.None の場合は何もしない
299 Assert.Equal(2, listView.TopItem.Index);
304 public void RestoreListViewScroll_ForceScrollTest()
306 this.UsingListView(count: 10, (listView, tab) =>
308 var listViewState = new TimelineListViewState(listView, tab);
310 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
311 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
313 var scrollState = new TimelineListViewState.ListViewScroll(
314 ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
315 TopItemStatusId: new TwitterStatusId("5")
317 listViewState.RestoreListViewScroll(scrollState, forceScroll: true);
319 // ScrollLockMode.None でも forceScroll が true の場合は FixedToItem 相当の動作になる
320 Assert.Equal(5, listView.TopItem.Index);
325 public void GetListViewSelection_EmptyTest()
327 this.UsingListView(count: 0, (listView, tab) =>
329 var listViewState = new TimelineListViewState(listView, tab);
331 var selectionState = listViewState.GetListViewSelection();
332 Assert.Empty(selectionState.SelectedStatusIds);
333 Assert.Null(selectionState.SelectionMarkStatusId);
334 Assert.Null(selectionState.FocusedStatusId);
339 public void GetListViewSelection_Test()
341 this.UsingListView(count: 10, (listView, tab) =>
343 var listViewState = new TimelineListViewState(listView, tab);
345 listView.SelectedIndices.Add(0);
346 listView.SelectedIndices.Add(2);
347 listView.SelectedIndices.Add(3);
348 tab.SelectPosts(new[] { 0, 2, 3 });
349 listView.SelectionMark = 1;
350 listView.FocusedItem = listView.Items[3];
352 var selectionState = listViewState.GetListViewSelection();
353 Assert.Equal(new TwitterStatusId[] { new("0"), new("2"), new("3") }, selectionState.SelectedStatusIds);
354 Assert.Equal(new TwitterStatusId("1"), selectionState.SelectionMarkStatusId);
355 Assert.Equal(new TwitterStatusId("3"), selectionState.FocusedStatusId);
360 public void RestoreListViewSelection_Test()
362 this.UsingListView(count: 10, (listView, tab) =>
364 var listViewState = new TimelineListViewState(listView, tab);
366 var selectionState = new TimelineListViewState.ListViewSelection(
367 SelectedStatusIds: new TwitterStatusId[] { new("1"), new("2"), new("3") },
368 SelectionMarkStatusId: new TwitterStatusId("1"),
369 FocusedStatusId: new TwitterStatusId("3")
371 listViewState.RestoreListViewSelection(selectionState);
373 Assert.Equal(new[] { 1, 2, 3 }, listView.SelectedIndices.Cast<int>());
374 Assert.Equal(1, listView.SelectionMark);
375 Assert.Equal(3, listView.FocusedItem?.Index);
380 public void RestoreListViewSelection_EmptyTest()
382 this.UsingListView(count: 0, (listView, tab) =>
384 var listViewState = new TimelineListViewState(listView, tab);
386 var selectionState = new TimelineListViewState.ListViewSelection(
387 SelectedStatusIds: Array.Empty<PostId>(),
388 SelectionMarkStatusId: null,
389 FocusedStatusId: null
391 listViewState.RestoreListViewSelection(selectionState);
393 Assert.Empty(listView.SelectedIndices);
394 Assert.Equal(-1, listView.SelectionMark);
395 Assert.Null(listView.FocusedItem?.Index);