From 817b6ac99e3f309bcdacdeebb6becf636ee28b11 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Mon, 13 May 2019 19:06:59 +0200
Subject: [PATCH] Prepare for UI tests

---
 .gitlab-ci.yml                                |   2 +-
 app/build.gradle.kts                          |   8 +
 .../quasseldroid/AccountBehaviorTest.kt       | 171 ++++++++++++++++++
 .../quasseldroid/ActivityLifecycleHandler.kt  |  43 +++++
 .../quasseldroid/QuasseldroidAndroidTest.kt   |  33 ++++
 .../kuschku/quasseldroid/util/TestRunner.kt   |  38 ++++
 .../de/kuschku/quasseldroid/util/ViewUtil.kt  |  26 +++
 .../conditionwatcher/ConditionWatcher.java    |  82 +++++++++
 .../util/conditionwatcher/Instruction.kt      |  38 ++++
 .../util/matcher/IsFullyRenderedMatcher.kt    |  51 ++++++
 .../de/kuschku/quasseldroid/Quasseldroid.kt   |   3 +-
 .../kuschku/quasseldroid/app/AppDelegate.kt   |   3 +-
 .../app/QuasseldroidBaseDelegate.kt           |  54 ++++++
 .../app/QuasseldroidReleaseDelegate.kt        |  37 +---
 .../accounts/selection/AccountAdapter.kt      |  12 +-
 .../app/QuasseldroidTestDelegate.kt           |   3 +-
 16 files changed, 562 insertions(+), 42 deletions(-)
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/AccountBehaviorTest.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/ActivityLifecycleHandler.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/QuasseldroidAndroidTest.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/util/TestRunner.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/util/ViewUtil.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/ConditionWatcher.java
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/Instruction.kt
 create mode 100644 app/src/androidTest/java/de/kuschku/quasseldroid/util/matcher/IsFullyRenderedMatcher.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidBaseDelegate.kt

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f5502c247..f78f4eefa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,7 +35,7 @@ build:
 test:
   stage: "test"
   script:
-  - "./gradlew check"
+    - "./gradlew check -x connectedCheck"
   artifacts:
     reports:
       junit: "*/build/test-results/*/TEST-*.xml"
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ab8a77718..afa441c01 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -61,6 +61,7 @@ android {
     testInstrumentationRunnerArguments = mapOf(
       "disableAnalytics" to "true"
     )
+    testInstrumentationRunner = "de.kuschku.quasseldroid.util.TestRunner"
   }
 
   buildTypes {
@@ -188,4 +189,11 @@ dependencies {
   testImplementation("org.robolectric", "robolectric", "4.2") {
     exclude(group = "org.threeten", module = "threetenbp")
   }
+
+  androidTestImplementation("junit", "junit", "4.12")
+  androidTestImplementation("androidx.test.espresso", "espresso-core", "3.1.0")
+  androidTestImplementation("androidx.test.espresso", "espresso-contrib", "3.1.0")
+  androidTestImplementation("androidx.test.ext", "junit", "1.1.0")
+  androidTestImplementation("androidx.test", "runner", "1.1.0")
+  androidTestImplementation("androidx.test", "rules", "1.1.0")
 }
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/AccountBehaviorTest.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/AccountBehaviorTest.kt
new file mode 100644
index 000000000..859ec8dd9
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/AccountBehaviorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.*
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.textfield.TextInputEditText
+import de.kuschku.quasseldroid.ui.chat.ChatActivity
+import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionActivity
+import de.kuschku.quasseldroid.util.conditionwatcher.ConditionWatcher.waitForCondition
+import de.kuschku.quasseldroid.util.conditionwatcher.Instruction
+import de.kuschku.quasseldroid.util.matcher.IsFullyRenderedMatcher.Companion.isFullyRendered
+import de.kuschku.quasseldroid.util.matches
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.Thread.sleep
+
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class AccountBehaviorTest {
+  @get:Rule
+  val activityActivityTestRule = ActivityTestRule(ChatActivity::class.java)
+
+  data class TestData(
+    val host: String,
+    val port: Int,
+    val requireSsl: Boolean,
+    val user: String,
+    val pass: String
+  )
+
+  @Test
+  fun actuallyTestThisThing() {
+    val testData = TestData(
+      host = InstrumentationRegistry.getArguments().getString("host", "localhost"),
+      port = InstrumentationRegistry.getArguments().getInt("port", 4242),
+      requireSsl = InstrumentationRegistry.getArguments().getBoolean("requireSsl"),
+      user = InstrumentationRegistry.getArguments().getString("user", "test"),
+      pass = InstrumentationRegistry.getArguments().getString("pass", "test")
+    )
+
+    onView(withId(R.id.host))
+      .perform(typeText(testData.host))
+
+    onView(withId(R.id.port))
+      .perform(
+        clearText(),
+        typeText(testData.port.toString())
+      )
+
+    if (testData.requireSsl) {
+      onView(withId(R.id.require_ssl))
+        .perform(click())
+    }
+
+    waitForCondition(1_000, Instruction("next button should be visible") {
+      activity?.findViewById<FloatingActionButton>(R.id.next_button).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.next_button))
+      .perform(click())
+
+    waitForCondition(1_000, Instruction("user input should be visible") {
+      activity?.findViewById<TextInputEditText>(R.id.user).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.user))
+      .perform(
+        clearText(),
+        typeText(testData.user)
+      )
+
+    onView(withId(R.id.pass))
+      .perform(
+        clearText(),
+        typeText(testData.pass)
+      )
+
+    waitForCondition(1_000, Instruction("next button should be visible") {
+      activity?.findViewById<FloatingActionButton>(R.id.next_button).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.next_button))
+      .perform(click())
+
+    waitForCondition(1_000, Instruction("name input should be visible") {
+      activity?.findViewById<TextInputEditText>(R.id.name).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.name))
+      .perform(
+        clearText(),
+        typeText("Remote Test")
+      )
+
+    waitForCondition(1_000, Instruction("next button should be visible") {
+      activity?.findViewById<FloatingActionButton>(R.id.next_button).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.next_button))
+      .perform(click())
+
+    waitForCondition(1_000, Instruction("selection activity should be shown") {
+      activity is AccountSelectionActivity
+    })
+
+    sleep(500)
+
+    onView(withId(R.id.account_list))
+      .perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
+
+    waitForCondition(1_000, Instruction("next button should be visible") {
+      activity?.findViewById<FloatingActionButton>(R.id.next_button).matches(
+        isFullyRendered()
+      )
+    })
+
+    onView(withId(R.id.next_button))
+      .perform(click())
+
+    waitForCondition(1_000, Instruction("chat activity should be shown") {
+      activity is ChatActivity
+    })
+
+    waitForCondition(10_000, Instruction("connection display should not be shown") {
+      activity?.findViewById<View>(R.id.connection_status).matches(
+        withEffectiveVisibility(ViewMatchers.Visibility.GONE)
+      )
+    })
+  }
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/ActivityLifecycleHandler.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/ActivityLifecycleHandler.kt
new file mode 100644
index 000000000..26a011c8c
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/ActivityLifecycleHandler.kt
@@ -0,0 +1,43 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+class ActivityLifecycleHandler : Application.ActivityLifecycleCallbacks {
+  var currentActivity: Activity? = null
+    private set
+
+  override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) = Unit
+  override fun onActivityStarted(activity: Activity?) = Unit
+  override fun onActivityResumed(activity: Activity?) {
+    currentActivity = activity
+  }
+
+  override fun onActivityPaused(activity: Activity?) {
+    currentActivity = null
+  }
+
+  override fun onActivityStopped(activity: Activity?) = Unit
+  override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) = Unit
+  override fun onActivityDestroyed(activity: Activity?) = Unit
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/QuasseldroidAndroidTest.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/QuasseldroidAndroidTest.kt
new file mode 100644
index 000000000..baa3637b6
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/QuasseldroidAndroidTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid
+
+import de.kuschku.quasseldroid.app.QuasseldroidBaseDelegate
+
+class QuasseldroidAndroidTest : Quasseldroid() {
+  val activityLifecycleHandler = ActivityLifecycleHandler()
+
+  override val delegate = QuasseldroidBaseDelegate(this)
+
+  override fun onCreate() {
+    super.onCreate()
+    registerActivityLifecycleCallbacks(activityLifecycleHandler)
+  }
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/util/TestRunner.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/util/TestRunner.kt
new file mode 100644
index 000000000..b40394b85
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/util/TestRunner.kt
@@ -0,0 +1,38 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util
+
+import android.app.Application
+import android.content.Context
+import androidx.test.runner.AndroidJUnitRunner
+import de.kuschku.quasseldroid.Quasseldroid
+import de.kuschku.quasseldroid.QuasseldroidAndroidTest
+
+class TestRunner : AndroidJUnitRunner() {
+  override fun newApplication(cl: ClassLoader?, className: String?,
+                              context: Context?): Application {
+    return super.newApplication(
+      cl,
+      if (className == Quasseldroid::class.java.canonicalName) QuasseldroidAndroidTest::class.java.canonicalName
+      else className,
+      context
+    )
+  }
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/util/ViewUtil.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/util/ViewUtil.kt
new file mode 100644
index 000000000..898a91bc6
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/util/ViewUtil.kt
@@ -0,0 +1,26 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util
+
+import org.hamcrest.Matcher
+
+fun <T> T?.matches(vararg matchers: Matcher<T>): Boolean = this != null && matchers.all {
+  it.matches(this)
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/ConditionWatcher.java b/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/ConditionWatcher.java
new file mode 100644
index 000000000..dd94f4403
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/ConditionWatcher.java
@@ -0,0 +1,82 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util.conditionwatcher;
+
+/**
+ * Created by F1sherKK on 08/10/15.
+ */
+public class ConditionWatcher {
+
+  public static final int CONDITION_NOT_MET = 0;
+  public static final int CONDITION_MET = 1;
+  public static final int TIMEOUT = 2;
+
+  public static final int DEFAULT_TIMEOUT_LIMIT = 1000 * 60;
+  public static final int DEFAULT_INTERVAL = 250;
+  private static ConditionWatcher conditionWatcher;
+  private int timeoutLimit = DEFAULT_TIMEOUT_LIMIT;
+  private int watchInterval = DEFAULT_INTERVAL;
+
+  private ConditionWatcher() {
+    super();
+  }
+
+  public static ConditionWatcher getInstance() {
+    if (conditionWatcher == null) {
+      conditionWatcher = new ConditionWatcher();
+    }
+    return conditionWatcher;
+  }
+
+  public static void waitForCondition(int timeoutLimit, Instruction instruction) throws Exception {
+    setTimeoutLimit(timeoutLimit);
+    waitForCondition(instruction);
+  }
+
+  public static void waitForCondition(Instruction instruction) throws Exception {
+    int status = CONDITION_NOT_MET;
+    int elapsedTime = 0;
+
+    do {
+      if (instruction.checkCondition()) {
+        status = CONDITION_MET;
+      } else {
+        elapsedTime += getInstance().watchInterval;
+        Thread.sleep(getInstance().watchInterval);
+      }
+
+      if (elapsedTime >= getInstance().timeoutLimit) {
+        status = TIMEOUT;
+        break;
+      }
+    } while (status != CONDITION_MET);
+
+    if (status == TIMEOUT)
+      throw new Exception(instruction.getDescription() + " - took more than " + getInstance().timeoutLimit / 1000 + " seconds. Test stopped.");
+  }
+
+  public static void setWatchInterval(int watchInterval) {
+    getInstance().watchInterval = watchInterval;
+  }
+
+  public static void setTimeoutLimit(int ms) {
+    getInstance().timeoutLimit = ms;
+  }
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/Instruction.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/Instruction.kt
new file mode 100644
index 000000000..44f12d0a2
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/util/conditionwatcher/Instruction.kt
@@ -0,0 +1,38 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util.conditionwatcher
+
+import androidx.test.platform.app.InstrumentationRegistry
+import de.kuschku.quasseldroid.QuasseldroidAndroidTest
+
+class Instruction(
+  val description: String,
+  private val condition: InstructionContext.() -> Boolean
+) {
+  fun checkCondition(): Boolean = condition.invoke(InstructionContext)
+
+  object InstructionContext {
+    val application
+      get() = (InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as QuasseldroidAndroidTest)
+
+    val activity
+      get() = application.activityLifecycleHandler.currentActivity
+  }
+}
diff --git a/app/src/androidTest/java/de/kuschku/quasseldroid/util/matcher/IsFullyRenderedMatcher.kt b/app/src/androidTest/java/de/kuschku/quasseldroid/util/matcher/IsFullyRenderedMatcher.kt
new file mode 100644
index 000000000..dbf0793fd
--- /dev/null
+++ b/app/src/androidTest/java/de/kuschku/quasseldroid/util/matcher/IsFullyRenderedMatcher.kt
@@ -0,0 +1,51 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util.matcher
+
+import android.view.View
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.remote.annotation.RemoteMsgConstructor
+import de.kuschku.quasseldroid.util.matches
+import org.hamcrest.Description
+import org.hamcrest.TypeSafeMatcher
+
+class IsFullyRenderedMatcher @RemoteMsgConstructor internal constructor(
+  areaPercentage: Int
+) : TypeSafeMatcher<View>() {
+
+  private val isDisplayingAtLeastMatcher = ViewMatchers.isDisplayingAtLeast(
+    areaPercentage)
+  private val withEffectiveVisibilityMatcher = ViewMatchers.withEffectiveVisibility(
+    ViewMatchers.Visibility.VISIBLE)
+
+  override fun describeTo(description: Description) {
+    description.appendText("view is fully rendered")
+  }
+
+  public override fun matchesSafely(view: View) = view.matches(
+    isDisplayingAtLeastMatcher,
+    withEffectiveVisibilityMatcher
+  )
+
+  companion object {
+    fun isFullyRendered(areaPercentage: Int = 100) = IsFullyRenderedMatcher(
+      areaPercentage)
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt b/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt
index 747366f5b..59d1f81e1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt
@@ -34,6 +34,7 @@ open class Quasseldroid : DaggerApplication() {
     super.onCreate()
     if (delegate.shouldInit()) {
       delegate.onInit()
+      delegate.onPreInit()
       applicationInjector().inject(this)
       delegate.onPostInit()
     }
@@ -41,6 +42,6 @@ open class Quasseldroid : DaggerApplication() {
 
   override fun attachBaseContext(base: Context) {
     super.attachBaseContext(LocaleHelper.setLocale(base))
-    delegate.onInstallMultidex()
+    delegate.onAttachBaseContext()
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/app/AppDelegate.kt b/app/src/main/java/de/kuschku/quasseldroid/app/AppDelegate.kt
index 482bb3550..2732e3d9f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/app/AppDelegate.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/app/AppDelegate.kt
@@ -21,7 +21,8 @@ package de.kuschku.quasseldroid.app
 
 interface AppDelegate {
   fun shouldInit(): Boolean
-  fun onInstallMultidex()
+  fun onAttachBaseContext()
+  fun onPreInit()
   fun onInit()
   fun onPostInit()
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidBaseDelegate.kt b/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidBaseDelegate.kt
new file mode 100644
index 000000000..2ae5e248e
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidBaseDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.app
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.multidex.MultiDex
+import de.kuschku.quasseldroid.BuildConfig
+import de.kuschku.quasseldroid.Quasseldroid
+import de.kuschku.quasseldroid.util.backport.AndroidThreeTenBackport
+import de.kuschku.quasseldroid.util.compatibility.AndroidCompatibilityUtils
+import de.kuschku.quasseldroid.util.compatibility.AndroidLoggingHandler
+import de.kuschku.quasseldroid.util.compatibility.AndroidStreamChannelFactory
+
+open class QuasseldroidBaseDelegate(private val app: Quasseldroid) : AppDelegate {
+  override fun shouldInit() = true
+
+  override fun onPreInit() = Unit
+
+  override fun onInit() {
+    // Init compatibility utils
+    AndroidCompatibilityUtils.inject()
+    AndroidLoggingHandler.inject()
+    AndroidStreamChannelFactory.inject()
+
+    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
+
+    AndroidThreeTenBackport.init(app)
+  }
+
+  override fun onPostInit() = Unit
+
+  override fun onAttachBaseContext() {
+    if (BuildConfig.DEBUG) {
+      MultiDex.install(app)
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidReleaseDelegate.kt b/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidReleaseDelegate.kt
index 5aea5d0f7..bfb5be46c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidReleaseDelegate.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/app/QuasseldroidReleaseDelegate.kt
@@ -21,8 +21,6 @@ package de.kuschku.quasseldroid.app
 
 import android.os.Build
 import android.os.StrictMode
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.multidex.MultiDex
 import com.squareup.leakcanary.LeakCanary
 import de.kuschku.malheur.CrashHandler
 import de.kuschku.quasseldroid.BuildConfig
@@ -34,45 +32,26 @@ import de.kuschku.quasseldroid.persistence.models.Account
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.SettingsMigration
 import de.kuschku.quasseldroid.settings.SettingsMigrationManager
-import de.kuschku.quasseldroid.util.backport.AndroidThreeTenBackport
-import de.kuschku.quasseldroid.util.compatibility.AndroidCompatibilityUtils
-import de.kuschku.quasseldroid.util.compatibility.AndroidLoggingHandler
-import de.kuschku.quasseldroid.util.compatibility.AndroidStreamChannelFactory
 
-class QuasseldroidReleaseDelegate(private val app: Quasseldroid) :
-  AppDelegate {
+class QuasseldroidReleaseDelegate(private val app: Quasseldroid) : QuasseldroidBaseDelegate(app) {
   override fun shouldInit() = !LeakCanary.isInAnalyzerProcess(app)
 
-  override fun onInit() {
+  override fun onPreInit() {
     LeakCanary.install(app)
-    // Normal app init code...
-
     CrashHandler.init<BuildConfig>(application = app)
-
-    // Init compatibility utils
-    AndroidCompatibilityUtils.inject()
-    AndroidLoggingHandler.inject()
-    AndroidStreamChannelFactory.inject()
-
-    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
-
-    AndroidThreeTenBackport.init(app)
   }
 
   override fun onPostInit() {
     // Migrate preferences
     SettingsMigrationManager(
       listOf(
-        SettingsMigration.migrationOf(0,
-                                      1) { prefs, edit ->
+        SettingsMigration.migrationOf(0, 1) { prefs, edit ->
           // Migrating database
-          val database = LegacyAccountDatabase.Creator.init(
-            app)
+          val database = LegacyAccountDatabase.Creator.init(app)
           val accounts = database.accounts().all()
           database.close()
 
-          val accountDatabase = AccountDatabase.Creator.init(
-            app)
+          val accountDatabase = AccountDatabase.Creator.init(app)
           accountDatabase.accounts().create(*accounts.map {
             Account(
               id = it.id,
@@ -205,10 +184,4 @@ class QuasseldroidReleaseDelegate(private val app: Quasseldroid) :
       )
     }
   }
-
-  override fun onInstallMultidex() {
-    if (BuildConfig.DEBUG) {
-      MultiDex.install(app)
-    }
-  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/accounts/selection/AccountAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/accounts/selection/AccountAdapter.kt
index dc922c52d..590ff921a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/accounts/selection/AccountAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/accounts/selection/AccountAdapter.kt
@@ -194,6 +194,8 @@ class AccountAdapter(
   }
 
   sealed class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+    internal var data: Account? = null
+
     class Item(itemView: View, actionListener: ItemListener, clickListener: ItemListener)
       : AccountViewHolder(itemView) {
       @BindView(R.id.account_name)
@@ -208,20 +210,18 @@ class AccountAdapter(
       @BindView(R.id.account_edit)
       lateinit var accountEdit: AppCompatImageButton
 
-      private var id = -1L
-
       init {
         ButterKnife.bind(this, itemView)
         accountEdit.setOnClickListener {
-          actionListener.onAction(id, adapterPosition)
+          actionListener.onAction(data?.id ?: -1L, adapterPosition)
         }
         itemView.setOnClickListener {
-          clickListener.onAction(id, adapterPosition)
+          clickListener.onAction(data?.id ?: -1L, adapterPosition)
         }
       }
 
       fun bind(account: Account, selected: Boolean) {
-        id = account.id
+        data = account
         accountName.text = account.name
         accountDescription.text = itemView.context.resources.getString(
           R.string.label_user_on_host, account.user, account.host, account.port
@@ -230,7 +230,7 @@ class AccountAdapter(
       }
 
       fun clear() {
-        id = -1L
+        data = null
         accountName.text = ""
         accountDescription.text = ""
         accountSelect.isChecked = false
diff --git a/app/src/test/java/de/kuschku/quasseldroid/app/QuasseldroidTestDelegate.kt b/app/src/test/java/de/kuschku/quasseldroid/app/QuasseldroidTestDelegate.kt
index 2341e01b8..541751f17 100644
--- a/app/src/test/java/de/kuschku/quasseldroid/app/QuasseldroidTestDelegate.kt
+++ b/app/src/test/java/de/kuschku/quasseldroid/app/QuasseldroidTestDelegate.kt
@@ -21,7 +21,8 @@ package de.kuschku.quasseldroid.app
 
 class QuasseldroidTestDelegate : AppDelegate {
   override fun shouldInit() = true
-  override fun onInstallMultidex() = Unit
+  override fun onAttachBaseContext() = Unit
+  override fun onPreInit() = Unit
   override fun onInit() = Unit
   override fun onPostInit() = Unit
 }
-- 
GitLab