1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 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.
23 using System.Collections.Generic;
25 using System.Net.Http;
27 using System.Threading;
28 using System.Threading.Tasks;
30 using OpenTween.Models;
31 using OpenTween.Thumbnail;
32 using OpenTween.Thumbnail.Services;
37 public class TweetThumbnailTest
39 private ThumbnailGenerator CreateThumbnailGenerator()
41 var imgAzyobuziNet = new ImgAzyobuziNet(autoupdate: false);
42 var thumbGenerator = new ThumbnailGenerator(imgAzyobuziNet);
43 thumbGenerator.Services.Clear();
44 return thumbGenerator;
47 private IThumbnailService CreateThumbnailService()
49 var thumbnailServiceMock = new Mock<IThumbnailService>();
52 x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
54 .ReturnsAsync(new MockThumbnailInfo
56 MediaPageUrl = "http://example.com/abcd",
57 ThumbnailImageUrl = "http://img.example.com/abcd.png",
61 x => x.GetThumbnailInfoAsync("http://example.com/efgh", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
63 .ReturnsAsync(new MockThumbnailInfo
65 MediaPageUrl = "http://example.com/efgh",
66 ThumbnailImageUrl = "http://img.example.com/efgh.png",
68 return thumbnailServiceMock.Object;
72 public async Task PrepareThumbnails_Test()
74 var thumbnailGenerator = this.CreateThumbnailGenerator();
75 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
77 var tweetThumbnail = new TweetThumbnail();
78 tweetThumbnail.Initialize(thumbnailGenerator);
80 var post = new PostClass
82 StatusId = new TwitterStatusId("100"),
83 Media = new() { new("http://example.com/abcd") },
86 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
88 Assert.True(tweetThumbnail.ThumbnailAvailable);
89 Assert.Single(tweetThumbnail.Thumbnails);
90 Assert.Equal(0, tweetThumbnail.SelectedIndex);
91 Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
92 Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
96 public async Task PrepareThumbnails_NoThumbnailTest()
98 var thumbnailGenerator = this.CreateThumbnailGenerator();
99 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
101 var tweetThumbnail = new TweetThumbnail();
102 tweetThumbnail.Initialize(thumbnailGenerator);
104 var post = new PostClass
106 StatusId = new TwitterStatusId("100"),
107 Media = new() { new("http://hoge.example.com/") },
110 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
112 Assert.False(tweetThumbnail.ThumbnailAvailable);
113 Assert.Throws<InvalidOperationException>(() => tweetThumbnail.Thumbnails);
117 public async Task PrepareThumbnails_CancelTest()
119 var thumbnailServiceMock = new Mock<IThumbnailService>();
122 x => x.GetThumbnailInfoAsync("http://slow.example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
126 await Task.Delay(200);
127 return new MockThumbnailInfo();
130 var thumbnailGenerator = this.CreateThumbnailGenerator();
131 thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
133 var tweetThumbnail = new TweetThumbnail();
134 tweetThumbnail.Initialize(thumbnailGenerator);
136 var post = new PostClass
138 StatusId = new TwitterStatusId("100"),
139 Media = new() { new("http://slow.example.com/abcd") },
142 using var tokenSource = new CancellationTokenSource();
143 var task = tweetThumbnail.PrepareThumbnails(post, tokenSource.Token);
144 tokenSource.Cancel();
146 await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await task);
147 Assert.True(task.IsCanceled);
151 public async Task LoadSelectedThumbnail_Test()
153 using var image = TestUtils.CreateDummyImage();
154 var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
157 x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
159 .ReturnsAsync(image);
161 var thumbnailServiceMock = new Mock<IThumbnailService>();
164 x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
166 .ReturnsAsync(thumbnailInfoMock.Object);
168 var thumbnailGenerator = this.CreateThumbnailGenerator();
169 thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
171 var tweetThumbnail = new TweetThumbnail();
172 tweetThumbnail.Initialize(thumbnailGenerator);
174 var post = new PostClass
176 StatusId = new TwitterStatusId("100"),
177 Media = new() { new("http://example.com/abcd") },
180 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
182 var loadedImage = await tweetThumbnail.LoadSelectedThumbnail();
183 Assert.Same(image, loadedImage);
187 public async Task LoadSelectedThumbnail_RequestCollapsingTest()
189 var tsc = new TaskCompletionSource<MemoryImage>();
190 var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
193 x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
197 var thumbnailServiceMock = new Mock<IThumbnailService>();
200 x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
202 .ReturnsAsync(thumbnailInfoMock.Object);
204 var thumbnailGenerator = this.CreateThumbnailGenerator();
205 thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
207 var tweetThumbnail = new TweetThumbnail();
208 tweetThumbnail.Initialize(thumbnailGenerator);
210 var post = new PostClass
212 StatusId = new TwitterStatusId("100"),
213 Media = new() { new("http://example.com/abcd") },
216 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
218 var loadTask1 = tweetThumbnail.LoadSelectedThumbnail();
219 await Task.Delay(50);
221 // 画像のロードが完了しない間に再度 LoadSelectedThumbnail が呼ばれた場合は同一の Task を返す
222 // (複数回呼ばれても画像のリクエストは一本にまとめられる)
223 var loadTask2 = tweetThumbnail.LoadSelectedThumbnail();
224 Assert.Same(loadTask1, loadTask2);
226 using var image = TestUtils.CreateDummyImage();
227 tsc.SetResult(image);
229 Assert.Same(image, await loadTask1);
233 public async Task SelectedIndex_Test()
235 var thumbnailGenerator = this.CreateThumbnailGenerator();
236 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
238 var tweetThumbnail = new TweetThumbnail();
239 tweetThumbnail.Initialize(thumbnailGenerator);
241 var post = new PostClass
243 StatusId = new TwitterStatusId("100"),
244 Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
247 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
249 Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
250 Assert.Equal(0, tweetThumbnail.SelectedIndex);
251 Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
253 tweetThumbnail.SelectedIndex = 1;
254 Assert.Equal(1, tweetThumbnail.SelectedIndex);
255 Assert.Equal("http://example.com/efgh", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
257 Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
258 Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 2);
262 public void SelectedIndex_NoThumbnailTest()
264 var thumbnailGenerator = this.CreateThumbnailGenerator();
265 var tweetThumbnail = new TweetThumbnail();
266 tweetThumbnail.Initialize(thumbnailGenerator);
268 Assert.False(tweetThumbnail.ThumbnailAvailable);
270 // サムネイルが無い場合に 0 以外の値をセットすると例外を発生させる
271 tweetThumbnail.SelectedIndex = 0;
272 Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
273 Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 1);
277 public async Task GetImageSearchUriGoogle_Test()
279 var thumbnailGenerator = this.CreateThumbnailGenerator();
280 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
282 var tweetThumbnail = new TweetThumbnail();
283 tweetThumbnail.Initialize(thumbnailGenerator);
285 var post = new PostClass
287 StatusId = new TwitterStatusId("100"),
288 Media = new() { new("http://example.com/abcd") },
291 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
293 Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
295 new(@"https://lens.google.com/uploadbyurl?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
296 tweetThumbnail.GetImageSearchUriGoogle()
301 public async Task GetImageSearchUriSauceNao_Test()
303 var thumbnailGenerator = this.CreateThumbnailGenerator();
304 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
306 var tweetThumbnail = new TweetThumbnail();
307 tweetThumbnail.Initialize(thumbnailGenerator);
309 var post = new PostClass
311 StatusId = new TwitterStatusId("100"),
312 Media = new() { new("http://example.com/abcd") },
315 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
317 Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
319 new(@"https://saucenao.com/search.php?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
320 tweetThumbnail.GetImageSearchUriSauceNao()
325 public async Task Scroll_Test()
327 var thumbnailGenerator = this.CreateThumbnailGenerator();
328 thumbnailGenerator.Services.Add(this.CreateThumbnailService());
330 var tweetThumbnail = new TweetThumbnail();
331 tweetThumbnail.Initialize(thumbnailGenerator);
333 var post = new PostClass
335 StatusId = new TwitterStatusId("100"),
336 Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
339 await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
341 Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
342 Assert.Equal(0, tweetThumbnail.SelectedIndex);
344 tweetThumbnail.ScrollDown();
345 Assert.Equal(1, tweetThumbnail.SelectedIndex);
347 tweetThumbnail.ScrollDown();
348 Assert.Equal(1, tweetThumbnail.SelectedIndex);
350 tweetThumbnail.ScrollUp();
351 Assert.Equal(0, tweetThumbnail.SelectedIndex);
353 tweetThumbnail.ScrollUp();
354 Assert.Equal(0, tweetThumbnail.SelectedIndex);
357 private class MockThumbnailInfo : ThumbnailInfo
359 public override Task<MemoryImage> LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken)
360 => Task.FromResult(TestUtils.CreateDummyImage());