Add wetter.de trigger
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 28 May 2016 16:24:28 +0000 (18:24 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 28 May 2016 16:28:22 +0000 (18:28 +0200)
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt [new file with mode: 0644]

diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt
new file mode 100644 (file)
index 0000000..39ec116
--- /dev/null
@@ -0,0 +1,84 @@
+package net.pterodactylus.rhynodge.webpages.weather.wetterde
+
+import kotlinx.html.body
+import kotlinx.html.div
+import kotlinx.html.h1
+import kotlinx.html.head
+import kotlinx.html.html
+import kotlinx.html.img
+import kotlinx.html.stream.createHTML
+import kotlinx.html.style
+import kotlinx.html.unsafe
+import net.pterodactylus.rhynodge.Reaction
+import net.pterodactylus.rhynodge.State
+import net.pterodactylus.rhynodge.Trigger
+import net.pterodactylus.rhynodge.output.DefaultOutput
+import net.pterodactylus.rhynodge.output.Output
+import java.text.DateFormat
+import java.time.temporal.ChronoUnit
+import java.util.Locale
+
+/**
+ * TODO
+ *
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+class WetterDeTrigger : Trigger {
+
+    private val dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH)
+    private lateinit var state: WetterDeState
+    private var changed = false
+
+    override fun mergeStates(previousState: State, currentState: State): State {
+        changed = previousState != currentState
+        state = currentState as WetterDeState
+        return currentState
+    }
+
+    override fun triggers(): Boolean {
+        return changed
+    }
+
+    override fun output(reaction: Reaction): Output {
+        val output = DefaultOutput("The Weather (according to wetter.de) on %s".format(dateFormatter.format(state.dateTime.toInstant().toEpochMilli())))
+        output.addText("text/html", generateHtmlOutput())
+        return output
+    }
+
+    private fun generateHtmlOutput(): String {
+        return createHTML().html {
+            head {
+                style("text/css") {
+                    unsafe {
+                        +".hour-state { display: table-row; } "
+                        +".hour-state > div { display: table-cell; padding-right: 1em; } "
+                    }
+                }
+            }
+            body {
+                val startTime = state.dateTime.toInstant()
+                h1 { +"The Weather (according to wetter.de) on %s".format(dateFormatter.format(startTime.toEpochMilli())) }
+                state.forEach {
+                    div("hour-state") {
+                        div("time") { +"%tH:%<tM".format(startTime.plus(it.hourIndex.toLong(), ChronoUnit.HOURS).toEpochMilli()) }
+                        div("temperature") { +"%d °C".format(it.temperature) }
+                        div("felt-temperature") { +"(%d °C)".format(it.feltTemperature) }
+                        div("rain-probability") { +"%d%%".format((it.rainProbability * 100).toInt()) }
+                        div("rain-amount") { +"%s l/m²".format(it.rainAmount.minDigits()) }
+                        div("wind-direction") { +it.windDirection.arrow }
+                        div("wind-speed") { +"%d km/h".format(it.windSpeed) }
+                        div("gust-speed") { +"(up to %d km/h)".format(it.gustSpeed) }
+                        div("humidity") { +"%d%%".format((it.humidity * 100).toInt()) }
+                        div("description") { +it.description }
+                        div("image") { img(src = it.image) }
+                    }
+                }
+            }
+        }.toString()
+    }
+
+    private fun Double.minDigits(): String {
+        return this.toString().replace(Regex("\\.0*$"), "")
+    }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt
new file mode 100644 (file)
index 0000000..ba5bf46
--- /dev/null
@@ -0,0 +1,87 @@
+package net.pterodactylus.rhynodge.webpages.weather.wetterde
+
+import net.pterodactylus.rhynodge.Reaction
+import net.pterodactylus.rhynodge.State
+import net.pterodactylus.rhynodge.webpages.weather.WindDirection
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.containsString
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.time.ZoneId.of
+import java.time.ZonedDateTime
+
+/**
+ * Unit test for [WetterDeTriggerTest].
+
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+class WetterDeTriggerTest {
+
+    private val now = ZonedDateTime.now(of("Europe/Berlin"))
+    private val previousState = WetterDeState(now.minusDays(1))
+    private val trigger = WetterDeTrigger()
+
+    @Test
+    fun currentStateIsAlwaysReturned() {
+        val currentState = WetterDeState(now)
+        assertThat(trigger.mergeStates(previousState, currentState), `is`(currentState as State))
+    }
+
+    @Test
+    fun triggerDoesNotTriggerIfStateHasNotChanged() {
+        val currentState = WetterDeState(now.minusDays(1))
+        trigger.mergeStates(previousState, currentState)
+        assertThat(trigger.triggers(), `is`(false))
+    }
+
+    @Test
+    fun triggerDoesTriggerIfStateHasChanged() {
+        val currentState = WetterDeState(now)
+        trigger.mergeStates(previousState, currentState)
+        assertThat(trigger.triggers(), `is`(true))
+    }
+
+    @Test
+    fun outputContainsCorrectSummary() {
+        val currentState = WetterDeState(ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
+        trigger.mergeStates(previousState, currentState)
+        val reaction = mock(Reaction::class.java)
+        val output = trigger.output(reaction)
+        assertThat(output.summary(), `is`("The Weather (according to wetter.de) on May 28, 2016"))
+    }
+
+    @Test
+    fun outputContainsCorrectHourData() {
+        val currentState = WetterDeState(ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
+        currentState += HourState(0, 10, 11, 0.12, 13.0, WindDirection.SOUTHSOUTHEAST, 14, 15, 0.16, "17", "http://18")
+        currentState += HourState(1, 20, 21, 0.22, 23.0, WindDirection.NORTHNORTHWEST, 24, 25, 0.26, "27", "http://28")
+        trigger.mergeStates(previousState, currentState)
+        val reaction = mock(Reaction::class.java)
+        val output = trigger.output(reaction)
+        val htmlText = output.text("text/html", -1)
+        assertThat(htmlText, containsString("00:00"))
+        assertThat(htmlText, containsString("10 °C"))
+        assertThat(htmlText, containsString("(11 °C)"))
+        assertThat(htmlText, containsString("12%"))
+        assertThat(htmlText, containsString("13 l/m²"))
+        assertThat(htmlText, containsString("↖↑"))
+        assertThat(htmlText, containsString("14 km/h"))
+        assertThat(htmlText, containsString("15 km/h"))
+        assertThat(htmlText, containsString("16%"))
+        assertThat(htmlText, containsString("17"))
+        assertThat(htmlText, containsString("http://18"))
+        assertThat(htmlText, containsString("01:00"))
+        assertThat(htmlText, containsString("20 °C"))
+        assertThat(htmlText, containsString("(21 °C)"))
+        assertThat(htmlText, containsString("22%"))
+        assertThat(htmlText, containsString("23 l/m²"))
+        assertThat(htmlText, containsString("↘↓"))
+        assertThat(htmlText, containsString("24 km/h"))
+        assertThat(htmlText, containsString("25 km/h"))
+        assertThat(htmlText, containsString("26%"))
+        assertThat(htmlText, containsString("27"))
+        assertThat(htmlText, containsString("http://28"))
+    }
+
+}