using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows.Forms;
using Moq;
using OpenTween.Models;
using OpenTween.Thumbnail;
using OpenTween.Thumbnail.Services;
using Xunit;
-using Xunit.Extensions;
namespace OpenTween
{
public class TweetThumbnailTest
{
- class TestThumbnailService : IThumbnailService
+ private ThumbnailGenerator CreateThumbnailGenerator()
{
- private readonly Regex regex;
- private readonly string replaceUrl;
- private readonly string replaceTooltip;
+ var imgAzyobuziNet = new ImgAzyobuziNet(autoupdate: false);
+ var thumbGenerator = new ThumbnailGenerator(imgAzyobuziNet);
+ thumbGenerator.Services.Clear();
+ return thumbGenerator;
+ }
- public TestThumbnailService(string pattern, string replaceUrl, string replaceTooltip)
- {
- this.regex = new Regex(pattern);
- this.replaceUrl = replaceUrl;
- this.replaceTooltip = replaceTooltip;
- }
+ private IThumbnailService CreateThumbnailService()
+ {
+ var thumbnailServiceMock = new Mock<IThumbnailService>();
+ thumbnailServiceMock
+ .Setup(
+ x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
+ )
+ .ReturnsAsync(new MockThumbnailInfo
+ {
+ MediaPageUrl = "http://example.com/abcd",
+ ThumbnailImageUrl = "http://img.example.com/abcd.png",
+ });
+ thumbnailServiceMock
+ .Setup(
+ x => x.GetThumbnailInfoAsync("http://example.com/efgh", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
+ )
+ .ReturnsAsync(new MockThumbnailInfo
+ {
+ MediaPageUrl = "http://example.com/efgh",
+ ThumbnailImageUrl = "http://img.example.com/efgh.png",
+ });
+ return thumbnailServiceMock.Object;
+ }
- public override async Task<ThumbnailInfo> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
- {
- var match = this.regex.Match(url);
+ [Fact]
+ public async Task PrepareThumbnails_Test()
+ {
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
- if (!match.Success) return null;
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
- if (url.StartsWith("http://slow.example.com/", StringComparison.Ordinal))
- await Task.Delay(1000, token).ConfigureAwait(false);
+ var post = new PostClass
+ {
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd") },
+ };
- return new MockThumbnailInfo
- {
- MediaPageUrl = url,
- ThumbnailImageUrl = match.Result(this.replaceUrl),
- TooltipText = this.replaceTooltip != null ? match.Result(this.replaceTooltip) : null,
- };
- }
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- class MockThumbnailInfo : ThumbnailInfo
- {
- public override Task<MemoryImage> LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken)
- {
- return Task.FromResult(TestUtils.CreateDummyImage());
- }
- }
+ Assert.True(tweetThumbnail.ThumbnailAvailable);
+ Assert.Single(tweetThumbnail.Thumbnails);
+ Assert.Equal(0, tweetThumbnail.SelectedIndex);
+ Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
+ Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
}
- public TweetThumbnailTest()
+ [Fact]
+ public async Task PrepareThumbnails_NoThumbnailTest()
{
- this.ThumbnailGeneratorSetup();
- this.MyCommonSetup();
- }
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
- private void ThumbnailGeneratorSetup()
- {
- ThumbnailGenerator.Services.Clear();
- ThumbnailGenerator.Services.AddRange(new[]
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
+ var post = new PostClass
{
- new TestThumbnailService(@"^https?://foo.example.com/(.+)$", @"http://img.example.com/${1}.png", null),
- new TestThumbnailService(@"^https?://bar.example.com/(.+)$", @"http://img.example.com/${1}.png", @"${1}"),
- new TestThumbnailService(@"^https?://slow.example.com/(.+)$", @"http://img.example.com/${1}.png", null),
- });
- }
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://hoge.example.com/") },
+ };
- private void MyCommonSetup()
- {
- var mockAssembly = new Mock<_Assembly>();
- mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- MyCommon.EntryAssembly = mockAssembly.Object;
+ Assert.False(tweetThumbnail.ThumbnailAvailable);
+ Assert.Throws<InvalidOperationException>(() => tweetThumbnail.Thumbnails);
}
[Fact]
- public void CreatePictureBoxTest()
+ public async Task PrepareThumbnails_CancelTest()
{
- using (var thumbBox = new TweetThumbnail())
+ var thumbnailServiceMock = new Mock<IThumbnailService>();
+ thumbnailServiceMock
+ .Setup(
+ x => x.GetThumbnailInfoAsync("http://slow.example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
+ )
+ .Returns(async () =>
+ {
+ await Task.Delay(200);
+ return new MockThumbnailInfo();
+ });
+
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
+
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
+ var post = new PostClass
{
- var method = typeof(TweetThumbnail).GetMethod("CreatePictureBox", BindingFlags.Instance | BindingFlags.NonPublic);
- var picbox = method.Invoke(thumbBox, new[] { "pictureBox1" }) as PictureBox;
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://slow.example.com/abcd") },
+ };
- Assert.NotNull(picbox);
- Assert.Equal("pictureBox1", picbox.Name);
- Assert.Equal(PictureBoxSizeMode.Zoom, picbox.SizeMode);
- Assert.False(picbox.WaitOnLoad);
- Assert.Equal(DockStyle.Fill, picbox.Dock);
+ using var tokenSource = new CancellationTokenSource();
+ var task = tweetThumbnail.PrepareThumbnails(post, tokenSource.Token);
+ tokenSource.Cancel();
- picbox.Dispose();
- }
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await task);
+ Assert.True(task.IsCanceled);
}
[Fact]
- public async Task CancelAsyncTest()
+ public async Task LoadSelectedThumbnail_Test()
{
+ using var image = TestUtils.CreateDummyImage();
+ var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
+ thumbnailInfoMock
+ .Setup(
+ x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
+ )
+ .ReturnsAsync(image);
+
+ var thumbnailServiceMock = new Mock<IThumbnailService>();
+ thumbnailServiceMock
+ .Setup(
+ x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
+ )
+ .ReturnsAsync(thumbnailInfoMock.Object);
+
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
+
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
var post = new PostClass
{
- TextFromApi = "てすと http://slow.example.com/abcd",
- Media = new List<MediaInfo>
- {
- new MediaInfo("http://slow.example.com/abcd"),
- },
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd") },
};
- using (var thumbbox = new TweetThumbnail())
- using (var tokenSource = new CancellationTokenSource())
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
- var task = thumbbox.ShowThumbnailAsync(post, tokenSource.Token);
-
- tokenSource.Cancel();
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await task);
- Assert.True(task.IsCanceled);
- }
+ var loadedImage = await tweetThumbnail.LoadSelectedThumbnail();
+ Assert.Same(image, loadedImage);
}
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- public void SetThumbnailCountTest(int count)
+ [Fact]
+ public async Task LoadSelectedThumbnail_RequestCollapsingTest()
{
- using (var thumbbox = new TweetThumbnail())
+ var tsc = new TaskCompletionSource<MemoryImage>();
+ var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
+ thumbnailInfoMock
+ .Setup(
+ x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
+ )
+ .Returns(tsc.Task);
+
+ var thumbnailServiceMock = new Mock<IThumbnailService>();
+ thumbnailServiceMock
+ .Setup(
+ x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
+ )
+ .ReturnsAsync(thumbnailInfoMock.Object);
+
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
+
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
+ var post = new PostClass
{
- var method = typeof(TweetThumbnail).GetMethod("SetThumbnailCount", BindingFlags.Instance | BindingFlags.NonPublic);
- method.Invoke(thumbbox, new[] { (object)count });
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd") },
+ };
- Assert.Equal(count, thumbbox.pictureBox.Count);
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- var num = 0;
- foreach (var picbox in thumbbox.pictureBox)
- {
- Assert.Equal("pictureBox" + num, picbox.Name);
- num++;
- }
+ var loadTask1 = tweetThumbnail.LoadSelectedThumbnail();
+ await Task.Delay(50);
- Assert.Equal(thumbbox.pictureBox, thumbbox.panelPictureBox.Controls.Cast<OTPictureBox>());
+ // 画像のロードが完了しない間に再度 LoadSelectedThumbnail が呼ばれた場合は同一の Task を返す
+ // (複数回呼ばれても画像のリクエストは一本にまとめられる)
+ var loadTask2 = tweetThumbnail.LoadSelectedThumbnail();
+ Assert.Same(loadTask1, loadTask2);
- Assert.Equal(0, thumbbox.scrollBar.Minimum);
+ using var image = TestUtils.CreateDummyImage();
+ tsc.SetResult(image);
- if (count == 0)
- Assert.Equal(0, thumbbox.scrollBar.Maximum);
- else
- Assert.Equal(count - 1, thumbbox.scrollBar.Maximum);
- }
+ Assert.Same(image, await loadTask1);
}
[Fact]
- public async Task ShowThumbnailAsyncTest()
+ public async Task SelectedIndex_Test()
{
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
+
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
var post = new PostClass
{
- TextFromApi = "てすと http://foo.example.com/abcd",
- Media = new List<MediaInfo>
- {
- new MediaInfo("http://foo.example.com/abcd"),
- },
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
};
- using (var thumbbox = new TweetThumbnail())
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
- await thumbbox.ShowThumbnailAsync(post);
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- Assert.Equal(0, thumbbox.scrollBar.Maximum);
- Assert.False(thumbbox.scrollBar.Enabled);
+ Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
+ Assert.Equal(0, tweetThumbnail.SelectedIndex);
+ Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
- Assert.Single(thumbbox.pictureBox);
- Assert.NotNull(thumbbox.pictureBox[0].Image);
+ tweetThumbnail.SelectedIndex = 1;
+ Assert.Equal(1, tweetThumbnail.SelectedIndex);
+ Assert.Equal("http://example.com/efgh", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
- Assert.IsAssignableFrom<ThumbnailInfo>(thumbbox.pictureBox[0].Tag);
- var thumbinfo = (ThumbnailInfo)thumbbox.pictureBox[0].Tag;
+ Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
+ Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 2);
+ }
+
+ [Fact]
+ public void SelectedIndex_NoThumbnailTest()
+ {
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
- Assert.Equal("http://foo.example.com/abcd", thumbinfo.MediaPageUrl);
- Assert.Equal("http://img.example.com/abcd.png", thumbinfo.ThumbnailImageUrl);
+ Assert.False(tweetThumbnail.ThumbnailAvailable);
- Assert.Equal("", thumbbox.toolTip.GetToolTip(thumbbox.pictureBox[0]));
- }
+ // サムネイルが無い場合に 0 以外の値をセットすると例外を発生させる
+ tweetThumbnail.SelectedIndex = 0;
+ Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
+ Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 1);
}
[Fact]
- public async Task ShowThumbnailAsyncTest2()
+ public async Task GetImageSearchUriGoogle_Test()
{
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
+
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
+
var post = new PostClass
{
- TextFromApi = "てすと http://foo.example.com/abcd http://bar.example.com/efgh",
- Media = new List<MediaInfo>
- {
- new MediaInfo("http://foo.example.com/abcd"),
- new MediaInfo("http://bar.example.com/efgh"),
- },
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd") },
};
- using (var thumbbox = new TweetThumbnail())
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
- await thumbbox.ShowThumbnailAsync(post);
-
- Assert.Equal(1, thumbbox.scrollBar.Maximum);
- Assert.True(thumbbox.scrollBar.Enabled);
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- Assert.Equal(2, thumbbox.pictureBox.Count);
- Assert.NotNull(thumbbox.pictureBox[0].Image);
- Assert.NotNull(thumbbox.pictureBox[1].Image);
+ Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
+ Assert.Equal(
+ new(@"https://lens.google.com/uploadbyurl?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
+ tweetThumbnail.GetImageSearchUriGoogle()
+ );
+ }
- Assert.IsAssignableFrom<ThumbnailInfo>(thumbbox.pictureBox[0].Tag);
- var thumbinfo = (ThumbnailInfo)thumbbox.pictureBox[0].Tag;
+ [Fact]
+ public async Task GetImageSearchUriSauceNao_Test()
+ {
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
- Assert.Equal("http://foo.example.com/abcd", thumbinfo.MediaPageUrl);
- Assert.Equal("http://img.example.com/abcd.png", thumbinfo.ThumbnailImageUrl);
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
- Assert.IsAssignableFrom<ThumbnailInfo>(thumbbox.pictureBox[1].Tag);
- thumbinfo = (ThumbnailInfo)thumbbox.pictureBox[1].Tag;
+ var post = new PostClass
+ {
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd") },
+ };
- Assert.Equal("http://bar.example.com/efgh", thumbinfo.MediaPageUrl);
- Assert.Equal("http://img.example.com/efgh.png", thumbinfo.ThumbnailImageUrl);
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
- Assert.Equal("", thumbbox.toolTip.GetToolTip(thumbbox.pictureBox[0]));
- Assert.Equal("efgh", thumbbox.toolTip.GetToolTip(thumbbox.pictureBox[1]));
- }
+ Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
+ Assert.Equal(
+ new(@"https://saucenao.com/search.php?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
+ tweetThumbnail.GetImageSearchUriSauceNao()
+ );
}
[Fact]
- public async Task ThumbnailLoadingEventTest()
+ public async Task Scroll_Test()
{
- using (var thumbbox = new TweetThumbnail())
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+ var thumbnailGenerator = this.CreateThumbnailGenerator();
+ thumbnailGenerator.Services.Add(this.CreateThumbnailService());
- var post = new PostClass
- {
- TextFromApi = "てすと",
- Media = new List<MediaInfo>
- {
- },
- };
- await TestUtils.NotRaisesAsync<EventArgs>(
- x => thumbbox.ThumbnailLoading += x,
- x => thumbbox.ThumbnailLoading -= x,
- () => thumbbox.ShowThumbnailAsync(post)
- );
-
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
-
- var post2 = new PostClass
- {
- TextFromApi = "てすと http://foo.example.com/abcd",
- Media = new List<MediaInfo>
- {
- new MediaInfo("http://foo.example.com/abcd"),
- },
- };
-
- await Assert.RaisesAsync<EventArgs>(
- x => thumbbox.ThumbnailLoading += x,
- x => thumbbox.ThumbnailLoading -= x,
- () => thumbbox.ShowThumbnailAsync(post2)
- );
- }
- }
+ var tweetThumbnail = new TweetThumbnail();
+ tweetThumbnail.Initialize(thumbnailGenerator);
- [Fact]
- public async Task ScrollTest()
- {
var post = new PostClass
{
- TextFromApi = "てすと http://foo.example.com/abcd http://foo.example.com/efgh",
- Media = new List<MediaInfo>
- {
- new MediaInfo("http://foo.example.com/abcd"),
- new MediaInfo("http://foo.example.com/efgh"),
- },
+ StatusId = new TwitterStatusId("100"),
+ Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
};
- using (var thumbbox = new TweetThumbnail())
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
- await thumbbox.ShowThumbnailAsync(post);
-
- Assert.Equal(0, thumbbox.scrollBar.Minimum);
- Assert.Equal(1, thumbbox.scrollBar.Maximum);
-
- thumbbox.scrollBar.Value = 0;
-
- thumbbox.ScrollDown();
- Assert.Equal(1, thumbbox.scrollBar.Value);
- Assert.False(thumbbox.pictureBox[0].Visible);
- Assert.True(thumbbox.pictureBox[1].Visible);
-
- thumbbox.ScrollDown();
- Assert.Equal(1, thumbbox.scrollBar.Value);
- Assert.False(thumbbox.pictureBox[0].Visible);
- Assert.True(thumbbox.pictureBox[1].Visible);
-
- thumbbox.ScrollUp();
- Assert.Equal(0, thumbbox.scrollBar.Value);
- Assert.True(thumbbox.pictureBox[0].Visible);
- Assert.False(thumbbox.pictureBox[1].Visible);
-
- thumbbox.ScrollUp();
- Assert.Equal(0, thumbbox.scrollBar.Value);
- Assert.True(thumbbox.pictureBox[0].Visible);
- Assert.False(thumbbox.pictureBox[1].Visible);
- }
+ await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
+
+ Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
+ Assert.Equal(0, tweetThumbnail.SelectedIndex);
+
+ tweetThumbnail.ScrollDown();
+ Assert.Equal(1, tweetThumbnail.SelectedIndex);
+
+ tweetThumbnail.ScrollDown();
+ Assert.Equal(1, tweetThumbnail.SelectedIndex);
+
+ tweetThumbnail.ScrollUp();
+ Assert.Equal(0, tweetThumbnail.SelectedIndex);
+
+ tweetThumbnail.ScrollUp();
+ Assert.Equal(0, tweetThumbnail.SelectedIndex);
+ }
+
+ private class MockThumbnailInfo : ThumbnailInfo
+ {
+ public override Task<MemoryImage> LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken)
+ => Task.FromResult(TestUtils.CreateDummyImage());
}
}
}