From: David ‘Bombe’ Roden Date: Sun, 13 Nov 2016 07:09:47 +0000 (+0100) Subject: Show loading animation while loading elements X-Git-Tag: 0.9.7^2~442 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=675710fc669a9f5ccfab42296a3aa0b822539e14;p=Sone.git Show loading animation while loading elements --- diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 046a377..50d3323 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -40,7 +40,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.core.ImageLoader; +import net.pterodactylus.sone.core.ElementLoader; import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent; import net.pterodactylus.sone.core.event.ImageInsertFailedEvent; import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent; @@ -85,7 +85,8 @@ import net.pterodactylus.sone.template.IdentityAccessor; import net.pterodactylus.sone.template.ImageAccessor; import net.pterodactylus.sone.template.ImageLinkFilter; import net.pterodactylus.sone.template.JavascriptFilter; -import net.pterodactylus.sone.template.LinkedImagesFilter; +import net.pterodactylus.sone.template.LinkedElementRenderFilter; +import net.pterodactylus.sone.template.LinkedElementsFilter; import net.pterodactylus.sone.template.ParserFilter; import net.pterodactylus.sone.template.PostAccessor; import net.pterodactylus.sone.template.ProfileAccessor; @@ -258,7 +259,7 @@ public class WebInterface { * The Sone plugin */ @Inject - public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter, ImageLoader imageLoader) { + public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter, ElementLoader elementLoader) { this.sonePlugin = sonePlugin; this.loaders = loaders; this.listNotificationFilter = listNotificationFilter; @@ -293,7 +294,8 @@ public class WebInterface { templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), soneTextParser)); templateContextFactory.addFilter("shorten", shortenFilter = new ShortenFilter()); templateContextFactory.addFilter("render", renderFilter = new RenderFilter(getCore(), templateContextFactory)); - templateContextFactory.addFilter("linked-images", new LinkedImagesFilter(imageLoader)); + templateContextFactory.addFilter("linked-elements", new LinkedElementsFilter(elementLoader)); + templateContextFactory.addFilter("render-linked-element", new LinkedElementRenderFilter(templateContextFactory)); templateContextFactory.addFilter("reparse", new ReparseFilter()); templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate")); templateContextFactory.addFilter("format", new FormatFilter()); diff --git a/src/main/kotlin/net/pterodactylus/sone/core/DefaultElementLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/DefaultElementLoader.kt new file mode 100644 index 0000000..eabfed8 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/DefaultElementLoader.kt @@ -0,0 +1,56 @@ +package net.pterodactylus.sone.core + +import com.google.common.cache.CacheBuilder +import freenet.keys.FreenetURI +import java.io.ByteArrayInputStream +import javax.imageio.ImageIO +import javax.inject.Inject + +/** + * [ElementLoader] implementation that uses a simple Guava [com.google.common.cache.Cache]. + */ +class DefaultElementLoader @Inject constructor(private val freenetInterface: FreenetInterface) : ElementLoader { + + private val loadingLinks = CacheBuilder.newBuilder().build() + private val imageCache = CacheBuilder.newBuilder().build() + private val callback = object : FreenetInterface.BackgroundFetchCallback { + override fun loaded(uri: FreenetURI, mimeType: String, data: ByteArray) { + if (!mimeType.startsWith("image/")) { + return + } + ByteArrayInputStream(data).use { + ImageIO.read(it) + }?.let { + imageCache.get(uri.toString()) { LinkedImage(uri.toString()) } + } + removeLoadingLink(uri) + } + + override fun failed(uri: FreenetURI) { + removeLoadingLink(uri) + } + + private fun removeLoadingLink(uri: FreenetURI) { + synchronized(loadingLinks) { + loadingLinks.invalidate(uri.toString()) + } + } + } + + override fun loadElement(link: String): LinkedElement { + synchronized(loadingLinks) { + imageCache.getIfPresent(link)?.run { + return this + } + if (loadingLinks.getIfPresent(link) == null) { + loadingLinks.put(link, true) + freenetInterface.startFetch(FreenetURI(link), callback) + } + } + return object : LinkedElement { + override val link = link + override val loading = true + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/core/DefaultImageLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/DefaultImageLoader.kt deleted file mode 100644 index 01e177a..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/core/DefaultImageLoader.kt +++ /dev/null @@ -1,39 +0,0 @@ -package net.pterodactylus.sone.core - -import com.google.common.cache.CacheBuilder -import freenet.keys.FreenetURI -import java.io.ByteArrayInputStream -import javax.imageio.ImageIO -import javax.inject.Inject - -/** - * [ImageLoader] implementation that uses a simple Guava [com.google.common.cache.Cache]. - */ -class DefaultImageLoader @Inject constructor(private val freenetInterface: FreenetInterface) : ImageLoader { - - private val imageCache = CacheBuilder.newBuilder().build() - private val callback = object : FreenetInterface.BackgroundFetchCallback { - override fun loaded(uri: FreenetURI, mimeType: String, data: ByteArray) { - if (!mimeType.startsWith("image/")) { - return - } - val image = ByteArrayInputStream(data).use { - ImageIO.read(it) - } - val loadedImage = LoadedImage(uri.toString(), mimeType, image.width, image.height) - imageCache.get(uri.toString()) { loadedImage } - } - - override fun failed(uri: FreenetURI) { - } - } - - override fun toLoadedImage(link: String): LoadedImage? { - imageCache.getIfPresent(link)?.run { - return this - } - freenetInterface.startFetch(FreenetURI(link), callback) - return null - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/core/ElementLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/ElementLoader.kt new file mode 100644 index 0000000..2424cf1 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/ElementLoader.kt @@ -0,0 +1,22 @@ +package net.pterodactylus.sone.core + +import com.google.inject.ImplementedBy + +/** + * Component that loads images and supplies information about them. + */ +@ImplementedBy(DefaultElementLoader::class) +interface ElementLoader { + + fun loadElement(link: String): LinkedElement + +} + +interface LinkedElement { + + val link: String + val loading: Boolean + +} + +data class LinkedImage(override val link: String, override val loading: Boolean = false) : LinkedElement diff --git a/src/main/kotlin/net/pterodactylus/sone/core/ImageLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/ImageLoader.kt deleted file mode 100644 index 5211015..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/core/ImageLoader.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.pterodactylus.sone.core - -import com.google.inject.ImplementedBy - -/** - * Component that loads images and supplies information about them. - */ -@ImplementedBy(DefaultImageLoader::class) -interface ImageLoader { - - fun toLoadedImage(link: String): LoadedImage? - -} - -data class LoadedImage(val link: String, val mimeType: String, val width: Int, val height: Int) diff --git a/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt b/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt new file mode 100644 index 0000000..6255f9d --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt @@ -0,0 +1,47 @@ +package net.pterodactylus.sone.template + +import net.pterodactylus.sone.core.LinkedElement +import net.pterodactylus.sone.core.LinkedImage +import net.pterodactylus.util.template.Filter +import net.pterodactylus.util.template.TemplateContext +import net.pterodactylus.util.template.TemplateContextFactory +import net.pterodactylus.util.template.TemplateParser +import java.io.StringReader +import java.io.StringWriter + +/** + * Renders all kinds of [LinkedElement]s. + */ +class LinkedElementRenderFilter(private val templateContextFactory: TemplateContextFactory) : Filter { + + companion object { + private val loadedImageTemplate = """""".parse() + private val notLoadedImageTemplate = """""".parse() + + private fun String.parse() = StringReader(this).use { TemplateParser.parse(it) } + } + + override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map?) = + when { + data is LinkedElement && data.loading -> renderNotLoadedLinkedElement(data) + data is LinkedImage -> renderLinkedImage(data) + else -> null + } + + private fun renderLinkedImage(linkedImage: LinkedImage) = + StringWriter().use { + val templateContext = templateContextFactory.createTemplateContext() + templateContext["link"] = linkedImage.link + loadedImageTemplate.render(templateContext, it) + it + }.toString() + + private fun renderNotLoadedLinkedElement(linkedElement: LinkedElement) = + StringWriter().use { + val templateContext = templateContextFactory.createTemplateContext() + templateContext["link"] = linkedElement.link + notLoadedImageTemplate.render(templateContext, it) + it + }.toString() + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt b/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt new file mode 100644 index 0000000..f95d6c0 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt @@ -0,0 +1,23 @@ +package net.pterodactylus.sone.template + +import net.pterodactylus.sone.core.ElementLoader +import net.pterodactylus.sone.core.LinkedElement +import net.pterodactylus.sone.text.FreenetLinkPart +import net.pterodactylus.sone.text.Part +import net.pterodactylus.util.template.Filter +import net.pterodactylus.util.template.TemplateContext + +/** + * Filter that takes a number of pre-rendered [Part]s and replaces all identified links to freenet elements + * with [LinkedElement]s. + */ +class LinkedElementsFilter(private val elementLoader: ElementLoader) : Filter { + + @Suppress("UNCHECKED_CAST") + override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap?) = + (data as? Iterable) + ?.filterIsInstance() + ?.map { elementLoader.loadElement(it.link) } + ?: listOf() + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/template/LinkedImagesFilter.kt b/src/main/kotlin/net/pterodactylus/sone/template/LinkedImagesFilter.kt deleted file mode 100644 index 584bec3..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/template/LinkedImagesFilter.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.pterodactylus.sone.template - -import net.pterodactylus.sone.core.ImageLoader -import net.pterodactylus.sone.core.LoadedImage -import net.pterodactylus.sone.text.FreenetLinkPart -import net.pterodactylus.sone.text.Part -import net.pterodactylus.util.template.Filter -import net.pterodactylus.util.template.TemplateContext - -/** - * Filter that takes a number of pre-rendered [Part]s and replaces all identified links to freenet images - * with [LoadedImage]s. - */ -class LinkedImagesFilter(private val imageLoader: ImageLoader) : Filter { - - @Suppress("UNCHECKED_CAST") - override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap?) = - (data as? Iterable) - ?.filterIsInstance() - ?.mapNotNull { imageLoader.toLoadedImage(it.link) } - ?: listOf() - -} diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index 8e008c7..34dad50 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -440,11 +440,11 @@ textarea { color: green; } -#sone .post .linked-images { +#sone .post .linked-elements { margin-top: 1ex; } -#sone .post .linked-image { +#sone .post .linked-element { display: inline-block; border: solid 1px black; width: 160px; @@ -511,11 +511,11 @@ textarea { font-size: inherit; } -#sone .post .reply .linked-images { +#sone .post .reply .linked-elements { margin-top: 1ex; } -#sone .post .reply .linked-image { +#sone .post .reply .linked-element { display: inline-block; border: solid 1px black; width: 120px; diff --git a/src/main/resources/static/images/loading-animation.gif b/src/main/resources/static/images/loading-animation.gif new file mode 100644 index 0000000..8d4f3f4 Binary files /dev/null and b/src/main/resources/static/images/loading-animation.gif differ diff --git a/src/main/resources/templates/include/viewPost.html b/src/main/resources/templates/include/viewPost.html index ac149f8..734a8b4 100644 --- a/src/main/resources/templates/include/viewPost.html +++ b/src/main/resources/templates/include/viewPost.html @@ -35,14 +35,12 @@
<% shortText>
<%if !shortText|match value=renderedText><%if !raw><%= View.Post.ShowMore|l10n|html><%/if><%/if> <%if !shortText|match value=renderedText><%if !raw><%/if><%/if> - <% parsedText|linked-images|store key==linkedImages> - <% foreach linkedImages linkedImage> + <% parsedText|linked-elements|store key==linkedElements> + <% foreach linkedElements linkedElement> <% first> -
+
<%/first> - - - + <% linkedElement|render-linked-element> <% last>
<%/last> diff --git a/src/main/resources/templates/include/viewReply.html b/src/main/resources/templates/include/viewReply.html index cd56e32..6091d40 100644 --- a/src/main/resources/templates/include/viewReply.html +++ b/src/main/resources/templates/include/viewReply.html @@ -23,14 +23,12 @@
<% shortText>
<%if !shortText|match value=renderedText><%if !raw><%= View.Post.ShowMore|l10n|html><%/if><%/if> <%if !shortText|match value=renderedText><%if !raw><%/if><%/if> - <% parsedText|linked-images|store key==linkedImages> - <% foreach linkedImages linkedImage> + <% parsedText|linked-elements|store key==linkedElements> + <% foreach linkedElements linkedElement> <% first> -
+
<%/first> - - - + <% linkedElement|render-linked-element> <% last>
<%/last> diff --git a/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt new file mode 100644 index 0000000..adc5bc9 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt @@ -0,0 +1,81 @@ +package net.pterodactylus.sone.core + +import com.google.common.io.ByteStreams +import com.google.common.io.Files +import freenet.keys.FreenetURI +import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback +import net.pterodactylus.sone.test.capture +import net.pterodactylus.sone.test.mock +import org.hamcrest.MatcherAssert +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.nullValue +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import java.io.ByteArrayOutputStream + +/** + * Unit test for [DefaultElementLoaderTest]. + */ +class DefaultElementLoaderTest { + + companion object { + private const val IMAGE_ID = "KSK@gpl.png" + } + + private val freenetInterface = mock() + private val elementLoader = DefaultElementLoader(freenetInterface) + private val callback = capture() + + @Test + fun `image loader starts request for link that is not known`() { + elementLoader.loadElement(IMAGE_ID) + verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), any()) + } + + @Test + fun `element loader only starts request once`() { + elementLoader.loadElement(IMAGE_ID) + elementLoader.loadElement(IMAGE_ID) + verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), any()) + } + + @Test + fun `element loader returns loading element on first call`() { + assertThat(elementLoader.loadElement(IMAGE_ID).loading, `is`(true)) + } + + @Test + fun `image loader can load image`() { + elementLoader.loadElement(IMAGE_ID) + verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), callback.capture()) + callback.value.loaded(FreenetURI(IMAGE_ID), "image/png", read("/static/images/unknown-image-0.png")) + val linkedElement = elementLoader.loadElement(IMAGE_ID) + assertThat(linkedElement.link, `is`(IMAGE_ID)) + assertThat(linkedElement.loading, `is`(false)) + assertThat(linkedElement, instanceOf(LinkedImage::class.java)) + } + + @Test + fun `image can be loaded again after it failed`() { + elementLoader.loadElement(IMAGE_ID) + verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), callback.capture()) + callback.value.failed(FreenetURI(IMAGE_ID)) + elementLoader.loadElement(IMAGE_ID) + verify(freenetInterface, times(2)).startFetch(eq(FreenetURI(IMAGE_ID)), callback.capture()) + } + + private fun read(resource: String): ByteArray = + javaClass.getResourceAsStream(resource)?.use { input -> + ByteArrayOutputStream().use { + ByteStreams.copy(input, it) + it + }.toByteArray() + } ?: ByteArray(0) + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/core/DefaultImageLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/DefaultImageLoaderTest.kt deleted file mode 100644 index cc31665..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/core/DefaultImageLoaderTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.pterodactylus.sone.core - -import com.google.common.io.ByteStreams -import com.google.common.io.Files -import freenet.keys.FreenetURI -import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback -import net.pterodactylus.sone.test.capture -import net.pterodactylus.sone.test.mock -import org.hamcrest.MatcherAssert -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers -import org.hamcrest.Matchers.`is` -import org.hamcrest.Matchers.nullValue -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.verify -import java.io.ByteArrayOutputStream - -/** - * Unit test for [DefaultImageLoaderTest]. - */ -class DefaultImageLoaderTest { - - companion object { - private const val IMAGE_ID = "KSK@gpl.png" - } - - private val freenetInterface = mock() - private val imageLoader = DefaultImageLoader(freenetInterface) - private val callback = capture() - - @Test - fun `image loader starts request for link that is not known`() { - assertThat(imageLoader.toLoadedImage(IMAGE_ID), nullValue()) - verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), any()) - } - - @Test - fun `image loader can load image`() { - assertThat(imageLoader.toLoadedImage(IMAGE_ID), nullValue()) - verify(freenetInterface).startFetch(eq(FreenetURI(IMAGE_ID)), callback.capture()) - callback.value.loaded(FreenetURI(IMAGE_ID), "image/png", read("/static/images/unknown-image-0.png")) - val loadedImage = imageLoader.toLoadedImage(IMAGE_ID)!! - assertThat(loadedImage.link, `is`(IMAGE_ID)) - assertThat(loadedImage.mimeType, `is`("image/png")) - assertThat(loadedImage.width, `is`(200)) - assertThat(loadedImage.height, `is`(150)) - } - - private fun read(resource: String): ByteArray = - javaClass.getResourceAsStream(resource)?.use { input -> - ByteArrayOutputStream().use { - ByteStreams.copy(input, it) - it - }.toByteArray() - } ?: ByteArray(0) - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/core/ElementLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/ElementLoaderTest.kt new file mode 100644 index 0000000..dbb5bcc --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/core/ElementLoaderTest.kt @@ -0,0 +1,20 @@ +package net.pterodactylus.sone.core + +import com.google.inject.Guice.createInjector +import net.pterodactylus.sone.test.bindMock +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.notNullValue +import org.junit.Test + +/** + * Unit test for [ElementLoader]. + */ +class ElementLoaderTest { + + @Test + fun `default image loader can be loaded by guice`() { + val injector = createInjector(bindMock()) + assertThat(injector.getInstance(ElementLoader::class.java), notNullValue()); + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/core/ImageLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/ImageLoaderTest.kt deleted file mode 100644 index d3a021c..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/core/ImageLoaderTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package net.pterodactylus.sone.core - -import com.google.inject.Guice.createInjector -import net.pterodactylus.sone.test.bindMock -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.notNullValue -import org.junit.Test - -/** - * Unit test for [ImageLoader]. - */ -class ImageLoaderTest { - - @Test - fun `default image loader can be loaded by guice`() { - val injector = createInjector(bindMock()) - assertThat(injector.getInstance(ImageLoader::class.java), notNullValue()); - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt new file mode 100644 index 0000000..20ee99a --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt @@ -0,0 +1,37 @@ +package net.pterodactylus.sone.template + +import net.pterodactylus.sone.core.LinkedImage +import net.pterodactylus.util.template.HtmlFilter +import net.pterodactylus.util.template.TemplateContextFactory +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` +import org.jsoup.Jsoup +import org.junit.Test + +/** + * Unit test for [LinkedElementRenderFilter]. + */ +class LinkedElementRenderFilterTest { + + private val templateContextFactory = TemplateContextFactory() + + init { + templateContextFactory.addFilter("html", HtmlFilter()) + } + + private val filter = LinkedElementRenderFilter(templateContextFactory) + + @Test + fun `filter can render linked images`() { + val html = filter.format(null, LinkedImage("KSK@gpl.png"), emptyMap()) as String + val linkNode = Jsoup.parseBodyFragment(html).body().child(0) + assertThat(linkNode.nodeName(), `is`("a")) + assertThat(linkNode.attr("href"), `is`("/KSK@gpl.png")) + val spanNode = linkNode.child(0) + assertThat(spanNode.nodeName(), `is`("span")) + assertThat(spanNode.attr("class"), `is`("linked-element")) + assertThat(spanNode.attr("title"), `is`("KSK@gpl.png")) + assertThat(spanNode.attr("style"), `is`("background-image: url('/KSK@gpl.png')")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt new file mode 100644 index 0000000..d519e41 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt @@ -0,0 +1,40 @@ +package net.pterodactylus.sone.template + +import net.pterodactylus.sone.core.ElementLoader +import net.pterodactylus.sone.core.LinkedElement +import net.pterodactylus.sone.core.LinkedImage +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.text.FreenetLinkPart +import net.pterodactylus.sone.text.LinkPart +import net.pterodactylus.sone.text.PlainTextPart +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.junit.Test +import org.mockito.Mockito.`when` + +/** + * Unit test for [LinkedElementsFilter]. + */ +class LinkedElementsFilterTest { + + private val imageLoader = mock() + private val filter = LinkedElementsFilter(imageLoader) + + @Test + fun `filter finds all loaded freenet images`() { + val parts = listOf( + PlainTextPart("text"), + LinkPart("http://link", "link"), + FreenetLinkPart("KSK@link", "link", false), + FreenetLinkPart("KSK@link.png", "link", false) + ) + `when`(imageLoader.loadElement("KSK@link")).thenReturn(LinkedImage("KSK@link", true)) + `when`(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedImage("KSK@link.png")) + val loadedImages = filter.format(null, parts, null) + assertThat(loadedImages, contains( + LinkedImage("KSK@link", true), + LinkedImage("KSK@link.png") + )) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/template/LinkedImagesFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/LinkedImagesFilterTest.kt deleted file mode 100644 index 8e86bd4..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/template/LinkedImagesFilterTest.kt +++ /dev/null @@ -1,37 +0,0 @@ -package net.pterodactylus.sone.template - -import net.pterodactylus.sone.core.ImageLoader -import net.pterodactylus.sone.core.LoadedImage -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.text.FreenetLinkPart -import net.pterodactylus.sone.text.LinkPart -import net.pterodactylus.sone.text.PlainTextPart -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers -import org.junit.Test -import org.mockito.Mockito.`when` - -/** - * Unit test for [LinkedImagesFilter]. - */ -class LinkedImagesFilterTest { - - private val imageLoader = mock() - private val filter = LinkedImagesFilter(imageLoader) - - @Test - fun `filter finds all loaded freenet images`() { - val parts = listOf( - PlainTextPart("text"), - LinkPart("http://link", "link"), - FreenetLinkPart("KSK@link", "link", false), - FreenetLinkPart("KSK@link.png", "link", false) - ) - `when`(imageLoader.toLoadedImage("KSK@link.png")).thenReturn(LoadedImage("KSK@link.png", "image/png", 1440, 900)) - val loadedImages = filter.format(null, parts, null) - assertThat(loadedImages, Matchers.contains( - LoadedImage("KSK@link.png", "image/png", 1440, 900) - )) - } - -}