OSDN Git Service

Merge pull request #307 from opentween/detect-rate-limits
[opentween/open-tween.git] / OpenTween.Tests / TimelineListViewStateTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Linq;
24 using System.Windows.Forms;
25 using OpenTween.Models;
26 using OpenTween.OpenTweenCustomControl;
27 using Xunit;
28
29 namespace OpenTween
30 {
31     public class TimelineListViewStateTest
32     {
33         [WinFormsFact]
34         public void Initialize_Test()
35         {
36             using var listView = new DetailsListView();
37             var tab = new PublicSearchTabModel("hoge");
38             var listViewState = new TimelineListViewState(listView, tab);
39         }
40
41         private void UsingListView(int count, Action<DetailsListView, TabModel> func)
42         {
43             using var listView = new DetailsListView();
44             listView.Columns.Add("col");
45             listView.HeaderStyle = ColumnHeaderStyle.None; // 座標計算の邪魔になるため非表示にする
46
47             listView.RetrieveVirtualItem += (s, e) => e.Item = new($"text {e.ItemIndex}");
48             listView.VirtualMode = true;
49             listView.VirtualListSize = count;
50
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();
55
56             var tab = new PublicSearchTabModel("hoge");
57             tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
58
59             var firstDateTime = new DateTimeUtc(2024, 1, 1, 0, 0, 0);
60             foreach (var i in MyCommon.CountUp(0, count - 1))
61             {
62                 var post = new PostClass
63                 {
64                     StatusId = new TwitterStatusId(i.ToString()),
65                     CreatedAtForSorting = firstDateTime + TimeSpan.FromSeconds(i),
66                 };
67                 tab.AddPostQueue(post);
68             }
69             tab.AddSubmit();
70
71             func(listView, tab);
72         }
73
74         [WinFormsFact]
75         public void GetScrollLockMode_IdAsc_Test()
76         {
77             this.UsingListView(count: 10, (listView, tab) =>
78             {
79                 var listViewState = new TimelineListViewState(listView, tab);
80
81                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
82                 listView.EnsureVisible(0); // 一番上にスクロール
83
84                 // 投稿日時の昇順に並んでいる場合、新着投稿によってスクロール位置がズレることがないため None を返す
85                 Assert.Equal(
86                     TimelineListViewState.ScrollLockMode.None,
87                     listViewState.GetScrollLockMode(lockScroll: false)
88                 );
89             });
90         }
91
92         [WinFormsFact]
93         public void GetScrollLockMode_IdAsc_BottomTest()
94         {
95             this.UsingListView(count: 10, (listView, tab) =>
96             {
97                 var listViewState = new TimelineListViewState(listView, tab);
98
99                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
100                 listView.EnsureVisible(9); // 一番下までスクロール
101
102                 // 最終行が表示されている場合はスクロール位置を一番下に固定する(新着投稿を表示し続ける)
103                 Assert.Equal(
104                     TimelineListViewState.ScrollLockMode.FixedToBottom,
105                     listViewState.GetScrollLockMode(lockScroll: false)
106                 );
107             });
108         }
109
110         [WinFormsFact]
111         public void GetScrollLockMode_IdAsc_BottomLockScrollTest()
112         {
113             this.UsingListView(count: 10, (listView, tab) =>
114             {
115                 var listViewState = new TimelineListViewState(listView, tab);
116
117                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
118                 listView.EnsureVisible(9); // 一番下までスクロール
119
120                 // 最終行が表示されていても lockScroll が true の場合は None を返す(新着投稿にスクロールしない)
121                 Assert.Equal(
122                     TimelineListViewState.ScrollLockMode.None,
123                     listViewState.GetScrollLockMode(lockScroll: true)
124                 );
125             });
126         }
127
128         [WinFormsFact]
129         public void GetScrollLockMode_IdDesc_TopTest()
130         {
131             this.UsingListView(count: 10, (listView, tab) =>
132             {
133                 var listViewState = new TimelineListViewState(listView, tab);
134
135                 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
136                 listView.EnsureVisible(0); // 一番上にスクロール
137
138                 // 投稿日時の降順に並んでいて 1 行目が表示されている場合はスクロール位置を一番上に固定する(新着投稿を表示し続ける)
139                 Assert.Equal(
140                     TimelineListViewState.ScrollLockMode.FixedToTop,
141                     listViewState.GetScrollLockMode(lockScroll: false)
142                 );
143             });
144         }
145
146         [WinFormsFact]
147         public void GetScrollLockMode_IdDesc_TopLockScrollTest()
148         {
149             this.UsingListView(count: 10, (listView, tab) =>
150             {
151                 var listViewState = new TimelineListViewState(listView, tab);
152
153                 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
154                 listView.EnsureVisible(0); // 一番上にスクロール
155
156                 // 先頭行が表示されていても lockScroll が true の場合は FixedToItem を返す(新着投稿にスクロールしない)
157                 Assert.Equal(
158                     TimelineListViewState.ScrollLockMode.FixedToItem,
159                     listViewState.GetScrollLockMode(lockScroll: true)
160                 );
161             });
162         }
163
164         [WinFormsFact]
165         public void GetScrollLockMode_IdDesc_BottomTest()
166         {
167             this.UsingListView(count: 10, (listView, tab) =>
168             {
169                 var listViewState = new TimelineListViewState(listView, tab);
170
171                 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending);
172                 listView.EnsureVisible(9); // 一番下にスクロール
173
174                 // 先頭行が表示されていない場合は FixedToItem を返す
175                 Assert.Equal(
176                     TimelineListViewState.ScrollLockMode.FixedToItem,
177                     listViewState.GetScrollLockMode(lockScroll: false)
178                 );
179             });
180         }
181
182         [WinFormsFact]
183         public void GetScrollLockMode_TextAsc_Test()
184         {
185             this.UsingListView(count: 10, (listView, tab) =>
186             {
187                 var listViewState = new TimelineListViewState(listView, tab);
188
189                 tab.SetSortMode(ComparerMode.Data, SortOrder.Ascending);
190                 listView.EnsureVisible(0); // 一番上にスクロール
191
192                 // ComparerMode.Id 以外の場合は常に FixedToItem を返す
193                 Assert.Equal(
194                     TimelineListViewState.ScrollLockMode.FixedToItem,
195                     listViewState.GetScrollLockMode(lockScroll: false)
196                 );
197             });
198         }
199
200         [WinFormsFact]
201         public void GetListViewScroll_Test()
202         {
203             this.UsingListView(count: 10, (listView, tab) =>
204             {
205                 var listViewState = new TimelineListViewState(listView, tab);
206
207                 tab.SetSortMode(ComparerMode.Id, SortOrder.Descending); // 投稿日時の降順
208                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
209
210                 var scrollState = listViewState.GetListViewScroll(lockScroll: false);
211                 Assert.Equal(
212                     TimelineListViewState.ScrollLockMode.FixedToItem,
213                     scrollState.ScrollLockMode
214                 );
215                 Assert.Equal(new TwitterStatusId("7"), scrollState.TopItemStatusId); // 3 行目の StatusId は "7"
216             });
217         }
218
219         [WinFormsFact]
220         public void RestoreListViewScroll_FixedToTopTest()
221         {
222             this.UsingListView(count: 10, (listView, tab) =>
223             {
224                 var listViewState = new TimelineListViewState(listView, tab);
225
226                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
227                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
228
229                 var scrollState = new TimelineListViewState.ListViewScroll(
230                     ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToTop,
231                     TopItemStatusId: null
232                 );
233                 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
234
235                 // 一番上にスクロールされた状態になる
236                 Assert.Equal(0, listView.TopItem.Index);
237             });
238         }
239
240         [WinFormsFact]
241         public void RestoreListViewScroll_FixedToBottomTest()
242         {
243             this.UsingListView(count: 10, (listView, tab) =>
244             {
245                 var listViewState = new TimelineListViewState(listView, tab);
246
247                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
248                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
249
250                 var scrollState = new TimelineListViewState.ListViewScroll(
251                     ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToBottom,
252                     TopItemStatusId: null
253                 );
254                 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
255
256                 // 一番下にスクロールされた状態になる(一番下に余白が生じるため null になる)
257                 Assert.Null(listView.GetItemAt(0, 82)?.Index);
258             });
259         }
260
261         [WinFormsFact]
262         public void RestoreListViewScroll_FixedToItemTest()
263         {
264             this.UsingListView(count: 10, (listView, tab) =>
265             {
266                 var listViewState = new TimelineListViewState(listView, tab);
267
268                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
269                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
270
271                 var scrollState = new TimelineListViewState.ListViewScroll(
272                     ScrollLockMode: TimelineListViewState.ScrollLockMode.FixedToItem,
273                     TopItemStatusId: new TwitterStatusId("5")
274                 );
275                 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
276
277                 // 6 行目が一番上になる位置にスクロールされた状態になる
278                 Assert.Equal(5, listView.TopItem.Index);
279             });
280         }
281
282         [WinFormsFact]
283         public void RestoreListViewScroll_NoneTest()
284         {
285             this.UsingListView(count: 10, (listView, tab) =>
286             {
287                 var listViewState = new TimelineListViewState(listView, tab);
288
289                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
290                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
291
292                 var scrollState = new TimelineListViewState.ListViewScroll(
293                     ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
294                     TopItemStatusId: new TwitterStatusId("5")
295                 );
296                 listViewState.RestoreListViewScroll(scrollState, forceScroll: false);
297
298                 // ScrollLockMode.None の場合は何もしない
299                 Assert.Equal(2, listView.TopItem.Index);
300             });
301         }
302
303         [WinFormsFact]
304         public void RestoreListViewScroll_ForceScrollTest()
305         {
306             this.UsingListView(count: 10, (listView, tab) =>
307             {
308                 var listViewState = new TimelineListViewState(listView, tab);
309
310                 tab.SetSortMode(ComparerMode.Id, SortOrder.Ascending);
311                 listView.TopItem = listView.Items[2]; // 3 行目が一番上になる位置にスクロールされた状態
312
313                 var scrollState = new TimelineListViewState.ListViewScroll(
314                     ScrollLockMode: TimelineListViewState.ScrollLockMode.None,
315                     TopItemStatusId: new TwitterStatusId("5")
316                 );
317                 listViewState.RestoreListViewScroll(scrollState, forceScroll: true);
318
319                 // ScrollLockMode.None でも forceScroll が true の場合は FixedToItem 相当の動作になる
320                 Assert.Equal(5, listView.TopItem.Index);
321             });
322         }
323
324         [WinFormsFact]
325         public void GetListViewSelection_EmptyTest()
326         {
327             this.UsingListView(count: 0, (listView, tab) =>
328             {
329                 var listViewState = new TimelineListViewState(listView, tab);
330
331                 var selectionState = listViewState.GetListViewSelection();
332                 Assert.Empty(selectionState.SelectedStatusIds);
333                 Assert.Null(selectionState.SelectionMarkStatusId);
334                 Assert.Null(selectionState.FocusedStatusId);
335             });
336         }
337
338         [WinFormsFact]
339         public void GetListViewSelection_Test()
340         {
341             this.UsingListView(count: 10, (listView, tab) =>
342             {
343                 var listViewState = new TimelineListViewState(listView, tab);
344
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];
351
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);
356             });
357         }
358
359         [WinFormsFact]
360         public void RestoreListViewSelection_Test()
361         {
362             this.UsingListView(count: 10, (listView, tab) =>
363             {
364                 var listViewState = new TimelineListViewState(listView, tab);
365
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")
370                 );
371                 listViewState.RestoreListViewSelection(selectionState);
372
373                 Assert.Equal(new[] { 1, 2, 3 }, listView.SelectedIndices.Cast<int>());
374                 Assert.Equal(1, listView.SelectionMark);
375                 Assert.Equal(3, listView.FocusedItem?.Index);
376             });
377         }
378
379         [WinFormsFact]
380         public void RestoreListViewSelection_EmptyTest()
381         {
382             this.UsingListView(count: 0, (listView, tab) =>
383             {
384                 var listViewState = new TimelineListViewState(listView, tab);
385
386                 var selectionState = new TimelineListViewState.ListViewSelection(
387                     SelectedStatusIds: Array.Empty<PostId>(),
388                     SelectionMarkStatusId: null,
389                     FocusedStatusId: null
390                 );
391                 listViewState.RestoreListViewSelection(selectionState);
392
393                 Assert.Empty(listView.SelectedIndices);
394                 Assert.Equal(-1, listView.SelectionMark);
395                 Assert.Null(listView.FocusedItem?.Index);
396             });
397         }
398     }
399 }