Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ android:
- build-tools-28.0.3
- android-19
- android-28
- android-29
- sys-img-armeabi-v7a-google_apis-19

before_script:
Expand Down
16 changes: 8 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId "bill.toybox"
minSdkVersion 19
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand All @@ -33,21 +33,21 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'

implementation project(':reaktive:reaktive')

implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation 'com.jakewharton.timber:timber:4.7.1'

testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.21.0'
testImplementation 'io.mockk:mockk:1.9'
testImplementation 'io.mockk:mockk:1.9.3'
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
}
8 changes: 8 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

#Glide
#-keep public class * implements com.bumptech.glide.module.GlideModule
#-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
93 changes: 93 additions & 0 deletions app/src/androidTest/java/bill/toybox/catbox/CatboxTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2019 Emanuel Machado da Silva <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package bill.toybox.catbox

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import bill.toybox.R
import bill.toybox.catbox.home.HomeActivity
import bill.toybox.catbox.settings.SettingsActivity
import bill.toybox.catbox.test.*
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import com.google.android.material.R as material

@RunWith(AndroidJUnit4::class)
class CatboxTests {

@get:Rule
val espressoTestRule = IntentsTestRule(HomeActivity::class.java)

@Test
fun testNavigatingToSettings() {
onView(withId(R.id.actionSettings))
.perform(click())

intended(hasComponent(SettingsActivity::class.java.name))
}

@Test
fun testFindingTheCat() {
onView(withText(R.string.box_number, 2)).perform(click())
onView(withId(material.id.snackbar_text))
.check(matches(withText(R.string.empty_box, "1st")))

onView(withId(material.id.snackbar_text))
.perform(awaitDisappear())

onView(withText(R.string.box_number, 2)).perform(click())

waitForView(withId(material.id.snackbar_text))
.check(matches(withQuantityText(R.plurals.cat_found, 2)))
}

@Test
fun testMultipleFailedAttempts() {
onView(withText(R.string.box_number, 1)).perform(click())
waitForView(withId(material.id.snackbar_text))
.check(matches(withText(R.string.empty_box, "1st")))

onView(withText(R.string.box_number, 1)).perform(click())
waitForView(withId(material.id.snackbar_text))
.check(matches(withText(R.string.empty_box, "2nd")))

onView(withText(R.string.box_number, 1)).perform(click())
waitForView(withId(material.id.snackbar_text))
.check(matches(withText(R.string.empty_box, "3rd")))
}

companion object {

@get:ClassRule
@JvmStatic
val boxCountSetter = SharedPreferencesTestRule("pref_boxCount", 3)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Emanuel Machado da Silva <[email protected]>
* Copyright (c) 2019 Emanuel Machado da Silva <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -20,34 +20,32 @@
* SOFTWARE.
*/

package bill.toybox.catbox.home
package bill.toybox.catbox

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import bill.toybox.R
import bill.toybox.catbox.home.HomeActivity
import bill.toybox.hub.HubActivity
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@LargeTest
class HomeNavigationTests {
class HubTests {

@get:Rule
val homeActivity = ActivityTestRule(HomeActivity::class.java)
val espressoTestRule = IntentsTestRule(HubActivity::class.java)

@Test
fun testNavigatingToSettings() {
onView(withId(R.id.actionSettings))
.perform(click())
fun testNavigatingToCatbox() {
onView(withId(R.id.catImage)).perform(click())

//TODO: Try and find a more reliable way of checking we moved screen
onView(withText(R.string.settings))
.check(matches(isDisplayed()))
Intents.intended(IntentMatchers.hasComponent(HomeActivity::class.java.name))
}
}
103 changes: 103 additions & 0 deletions app/src/androidTest/java/bill/toybox/catbox/test/EspressoExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2019 Emanuel Machado da Silva <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package bill.toybox.catbox.test

import android.view.View
import androidx.annotation.StringRes
import androidx.test.espresso.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.TreeIterables
import androidx.test.platform.app.InstrumentationRegistry
import org.hamcrest.Matcher
import java.lang.Thread.sleep

fun withText(@StringRes resId: Int, vararg formatArgs: Any): Matcher<View> {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val text = context.getString(resId, *formatArgs)
return ViewMatchers.withText(text)
}

fun withQuantityText(@StringRes resId: Int, vararg formatArgs: Int): Matcher<View> {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val text = context.resources.getQuantityString(resId, formatArgs[0], *formatArgs.toTypedArray())
return ViewMatchers.withText(text)
}

fun awaitDisappear(): ViewAction = AwaitDisappearViewAction()

private class AwaitDisappearViewAction : ViewAction {
override fun getDescription() = "await until disappear"

override fun getConstraints(): Matcher<View> = ViewMatchers.isDisplayed()

// FIXME: Should have a timeout
override fun perform(uiController: UiController, view: View) {
val rootView = view.rootView
val isDisplayedMatcher = ViewMatchers.isDisplayed()
while (view.isDescendantOf(rootView) && isDisplayedMatcher.matches(view)) {
uiController.loopMainThreadForAtLeast(200)
}
}

private fun View.isDescendantOf(rootView: View): Boolean {
if (this == rootView) return true

var view = this
while (view.parent is View) {
if (view.parent == rootView) return true
view = view.parent as View
}

return false
}
}

// FIXME: Should have a timeout
fun waitForView(viewMatcher: Matcher<View>): ViewInteraction {
while (true) {
try {
onView(isRoot()).perform(LookForViewViewAction(viewMatcher))
return onView(viewMatcher)
} catch (e: Exception) {
sleep(250)
}
}
}

private class LookForViewViewAction(val viewMatcher: Matcher<View>) : ViewAction {

override fun getDescription() = "Looking for $viewMatcher in the root view"

override fun getConstraints(): Matcher<View>? = isRoot()

override fun perform(uiController: UiController, view: View) {
if (TreeIterables.breadthFirstViewTraversal(view).none(viewMatcher::matches)) {
throw NoMatchingViewException.Builder()
.withRootView(view)
.withViewMatcher(viewMatcher)
.build()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019 Emanuel Machado da Silva <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package bill.toybox.catbox.test

import android.preference.PreferenceManager
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

class SharedPreferencesTestRule(val key: String, val value: Int) : TestRule {

override fun apply(base: Statement, description: Description) = object : Statement() {
override fun evaluate() {
val context = InstrumentationRegistry.getInstrumentation().targetContext;

val prefManager = PreferenceManager.getDefaultSharedPreferences(context)
val oldValue = prefManager.getInt(key, 5)

prefManager.edit().apply {
putInt(key, value)
commit()
}

base.evaluate()

prefManager.edit().apply {
putInt(key, oldValue)
commit()
}

}
}
}
10 changes: 9 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="bill.toybox">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".CatBoxApplication"
android:allowBackup="true"
Expand All @@ -32,13 +34,19 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".catbox.home.HomeActivity">

<activity android:name=".hub.HubActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".catbox.home.HomeActivity"
android:label="@string/catbox_title"
android:parentActivityName=".hub.HubActivity" />

<activity
android:name=".catbox.settings.SettingsActivity"
android:label="@string/settings"
Expand Down
Loading