mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
Ci integration upgrade (#14)
- Circleci setup - gradle version updated - snapshots added - separated test files by flavour
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
// kotlin kapt
|
||||
apply plugin: 'kotlin-kapt'
|
||||
// Android navigation
|
||||
apply plugin: 'androidx.navigation.safeargs'
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'androidx.navigation.safeargs'
|
||||
}
|
||||
android {
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
compileSdkVersion 32
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
applicationId "com.appttude.h_mal.atlas_weather"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 32
|
||||
targetSdkVersion 33
|
||||
versionCode 5
|
||||
versionName "3.0"
|
||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||
@@ -65,12 +64,16 @@ android {
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
productFlavors{
|
||||
atlasWeather{
|
||||
applicationIdSuffix ".atlasWeather"
|
||||
productFlavors {
|
||||
atlasWeather {
|
||||
applicationId "com.appttude.h_mal.atlas_weather"
|
||||
versionCode 5
|
||||
versionName "3.0.0"
|
||||
}
|
||||
monoWeather{
|
||||
applicationIdSuffix ".monoWeather"
|
||||
monoWeather {
|
||||
applicationId "com.appttude.h_mal.atlas_weather.monoWeather"
|
||||
versionCode 5
|
||||
versionName "3.0.0"
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
@@ -86,8 +89,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -115,16 +116,28 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// android unit testing and espresso
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.1-alpha06'
|
||||
androidTestImplementation "androidx.test:core:1.4.0"
|
||||
|
||||
/ * Android Espresso */
|
||||
def testJunitVersion = "1.1.5"
|
||||
def testRunnerVersion = "1.5.2"
|
||||
def espressoVersion = "3.5.1"
|
||||
androidTestImplementation "androidx.test.ext:junit:$testJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
|
||||
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
|
||||
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
||||
androidTestImplementation "org.hamcrest:hamcrest:2.2"
|
||||
//mock websever for testing retrofit responses
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
||||
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
||||
|
||||
//mockito and livedata testing
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
implementation 'android.arch.core:core-testing'
|
||||
implementation 'androidx.arch.core:core-testing:2.2.0'
|
||||
|
||||
// Mockk
|
||||
def mockk_ver = "1.10.5"
|
||||
@@ -171,4 +184,6 @@ dependencies {
|
||||
/ * Glide */
|
||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||
/ * screenshot library */
|
||||
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
//package com.appttude.h_mal.atlas_weather
|
||||
//
|
||||
//import android.Manifest
|
||||
//import android.R
|
||||
//import android.app.Activity
|
||||
//import android.content.Context
|
||||
//import android.os.Build
|
||||
//import android.view.View
|
||||
//import android.view.WindowManager
|
||||
//import androidx.annotation.StringRes
|
||||
//import androidx.test.core.app.ActivityScenario
|
||||
//import androidx.test.espresso.*
|
||||
//import androidx.test.espresso.Espresso.onView
|
||||
//import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
//import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
//import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
//import androidx.test.rule.GrantPermissionRule
|
||||
//import com.appttude.h_mal.atlas_weather.atlasWeather.ui.BaseActivity
|
||||
////import h_mal.appttude.com.driver.base.BaseActivity
|
||||
//import com.appttude.h_mal.atlas_weather.helpers.BaseViewAction
|
||||
//import com.appttude.h_mal.atlas_weather.helpers.SnapshotRule
|
||||
//import org.hamcrest.CoreMatchers
|
||||
//import org.hamcrest.Description
|
||||
//import org.hamcrest.Matcher
|
||||
//import org.hamcrest.TypeSafeMatcher
|
||||
//import org.hamcrest.core.AllOf
|
||||
//import org.junit.After
|
||||
//import org.junit.Before
|
||||
//import org.junit.Rule
|
||||
//import tools.fastlane.screengrab.Screengrab
|
||||
//import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
|
||||
//import tools.fastlane.screengrab.locale.LocaleTestRule
|
||||
//
|
||||
//
|
||||
//open class BaseUiTest<T : BaseActivity>(
|
||||
// private val activity: Class<T>
|
||||
//) {
|
||||
//
|
||||
// private lateinit var mActivityScenarioRule: ActivityScenario<T>
|
||||
// private var mIdlingResource: IdlingResource? = null
|
||||
//
|
||||
// private lateinit var currentActivity: Activity
|
||||
//
|
||||
// @get:Rule
|
||||
// var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
//
|
||||
// @get:Rule
|
||||
// var snapshotRule: SnapshotRule = SnapshotRule()
|
||||
//
|
||||
// @Rule
|
||||
// @JvmField
|
||||
// var localeTestRule = LocaleTestRule()
|
||||
//
|
||||
// @Before
|
||||
// fun setup() {
|
||||
// Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
|
||||
// beforeLaunch()
|
||||
// mActivityScenarioRule = ActivityScenario.launch(activity)
|
||||
// mActivityScenarioRule.onActivity {
|
||||
//// mIdlingResource = it.getIdlingResource()!!
|
||||
//// IdlingRegistry.getInstance().register(mIdlingResource)
|
||||
// afterLaunch(it)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @After
|
||||
// fun tearDown() {
|
||||
// mIdlingResource?.let {
|
||||
// IdlingRegistry.getInstance().unregister(it)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun getResourceString(@StringRes stringRes: Int): String {
|
||||
// return getInstrumentation().targetContext.resources.getString(
|
||||
// stringRes
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// fun waitFor(delay: Long) {
|
||||
// onView(isRoot()).perform(object : ViewAction {
|
||||
// override fun getConstraints(): Matcher<View> = isRoot()
|
||||
// override fun getDescription(): String = "wait for $delay milliseconds"
|
||||
// override fun perform(uiController: UiController, v: View?) {
|
||||
// uiController.loopMainThreadForAtLeast(delay)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// open fun beforeLaunch() {}
|
||||
// open fun afterLaunch(context: Context) {}
|
||||
//
|
||||
//
|
||||
// @Suppress("DEPRECATION")
|
||||
// fun checkToastMessage(message: String) {
|
||||
// onView(withText(message)).inRoot(object : TypeSafeMatcher<Root>() {
|
||||
// override fun describeTo(description: Description?) {
|
||||
// description?.appendText("is toast")
|
||||
// }
|
||||
//
|
||||
// override fun matchesSafely(root: Root): Boolean {
|
||||
// root.run {
|
||||
// if (windowLayoutParams.get().type == WindowManager.LayoutParams.TYPE_TOAST) {
|
||||
// decorView.run {
|
||||
// if (windowToken === applicationWindowToken) {
|
||||
// // windowToken == appToken means this window isn't contained by any other windows.
|
||||
// // if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
// ).check(matches(isDisplayed()))
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
// waitFor(3500)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun checkSnackBarDisplayedByMessage(message: String) {
|
||||
// onView(
|
||||
// CoreMatchers.allOf(
|
||||
// withId(com.google.android.material.R.id.snackbar_text),
|
||||
// withText(message)
|
||||
// )
|
||||
// ).check(matches(isDisplayed()))
|
||||
// }
|
||||
//
|
||||
// private fun getCurrentActivity(): Activity {
|
||||
// onView(AllOf.allOf(withId(R.id.content), isDisplayed()))
|
||||
// .perform(object : BaseViewAction() {
|
||||
// override fun setPerform(uiController: UiController?, view: View?) {
|
||||
// if (view?.context is Activity) {
|
||||
// currentActivity = view.context as Activity
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return currentActivity
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.appttude.h_mal.atlas_weather
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
|
||||
/**
|
||||
* Matcher for testing error of TextInputLayout
|
||||
*/
|
||||
fun checkErrorMessage(expectedErrorText: String): Matcher<View?> {
|
||||
return object : TypeSafeMatcher<View?>() {
|
||||
override fun matchesSafely(view: View?): Boolean {
|
||||
if (view is EditText) {
|
||||
return view.error.toString() == expectedErrorText
|
||||
}
|
||||
|
||||
if (view !is TextInputLayout) return false
|
||||
|
||||
val error = view.error ?: return false
|
||||
return expectedErrorText == error.toString()
|
||||
}
|
||||
|
||||
override fun describeTo(d: Description?) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkImage(): Matcher<View?> {
|
||||
return object : TypeSafeMatcher<View?>() {
|
||||
override fun matchesSafely(view: View?): Boolean {
|
||||
if (view is ImageView) {
|
||||
return hasImage(view)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun describeTo(d: Description?) {}
|
||||
|
||||
private fun hasImage(view: ImageView): Boolean {
|
||||
val drawable = view.drawable
|
||||
var hasImage = drawable != null
|
||||
if (hasImage && drawable is BitmapDrawable) {
|
||||
hasImage = drawable.bitmap != null
|
||||
}
|
||||
return hasImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.hamcrest.Matcher.*
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -27,7 +26,8 @@ class LocationProviderImplTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
|
||||
val appContext =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
|
||||
locationProvider = LocationProviderImpl(appContext)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class LocationProviderImplTest {
|
||||
try {
|
||||
// Act
|
||||
locationProvider.getLatLongFromLocationName(randomString)
|
||||
}catch (e: IOException){
|
||||
} catch (e: IOException) {
|
||||
// Assert
|
||||
assertEquals(e.message, "No location found")
|
||||
}
|
||||
@@ -70,16 +70,14 @@ class LocationProviderImplTest {
|
||||
@Test
|
||||
fun getLocationNameFromLatLong_locationTypeIsCity_correctLocationReturned() = runBlocking {
|
||||
// Act
|
||||
val retrievedLocation = locationProvider.getLocationNameFromLatLong(lat, long, LocationType.City)
|
||||
val retrievedLocation =
|
||||
locationProvider.getLocationNameFromLatLong(lat, long, LocationType.City)
|
||||
|
||||
// Assert
|
||||
assertEquals(retrievedLocation, city)
|
||||
}
|
||||
|
||||
private fun assertRangeOfDouble(input: Double, expected: Double, range: Double) {
|
||||
assertThat(expected, allOf(
|
||||
greaterThanOrEqualTo(input - range),
|
||||
lessThanOrEqualTo(input + range))
|
||||
)
|
||||
assertEquals(expected, input, range)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import android.view.View
|
||||
import org.hamcrest.BaseMatcher
|
||||
import org.hamcrest.Description
|
||||
|
||||
class BaseMatcher: BaseMatcher<View>() {
|
||||
override fun describeTo(description: Description?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun matches(actual: Any?): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
open class BaseViewAction: ViewAction {
|
||||
override fun getDescription(): String? = setDescription()
|
||||
|
||||
override fun getConstraints(): Matcher<View> = setConstraints()
|
||||
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
setPerform(uiController, view)
|
||||
}
|
||||
|
||||
open fun setDescription(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
open fun setConstraints(): Matcher<View> {
|
||||
return isAssignableFrom(View::class.java)
|
||||
}
|
||||
|
||||
open fun setPerform(uiController: UiController?, view: View?) { }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* File paths for images on device
|
||||
*/
|
||||
fun getImagePath(imageConst: String): String {
|
||||
return File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||
"/Camera/images/$imageConst"
|
||||
).absolutePath
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipData.Item
|
||||
import android.net.Uri
|
||||
import java.io.File
|
||||
|
||||
object DataHelper {
|
||||
|
||||
fun createClipItem(filePath: String) = Item(
|
||||
Uri.fromFile(
|
||||
File(filePath)
|
||||
)
|
||||
)
|
||||
|
||||
fun createClipData(item: Item, mimeType: String = "text/uri-list") =
|
||||
ClipData(null, arrayOf(mimeType), item)
|
||||
|
||||
fun createClipData(filePath: String) = createClipData(createClipItem(filePath))
|
||||
|
||||
fun createClipData(filePaths: Array<String>): ClipData {
|
||||
val clipData = createClipData(filePaths[0])
|
||||
val remainingFiles = filePaths.copyOfRange(1, filePaths.size - 1)
|
||||
clipData.addFilePaths(remainingFiles)
|
||||
return clipData
|
||||
}
|
||||
|
||||
fun createClipData(uri: Uri) = createClipData(Item(uri))
|
||||
|
||||
fun ClipData.addFilePaths(filePaths: Array<String>) {
|
||||
filePaths.forEach { addItem(createClipItem(it)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import android.os.SystemClock.sleep
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.Checkable
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.NoMatchingViewException
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.util.TreeIterables
|
||||
import org.hamcrest.BaseMatcher
|
||||
import org.hamcrest.CoreMatchers.isA
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
|
||||
object EspressoHelper {
|
||||
|
||||
/**
|
||||
* Perform action of waiting for a certain view within a single root view
|
||||
* @param viewMatcher Generic Matcher used to find our view
|
||||
*/
|
||||
fun searchFor(viewMatcher: Matcher<View>): ViewAction {
|
||||
|
||||
return object : ViewAction {
|
||||
|
||||
override fun getConstraints(): Matcher<View> = isRoot()
|
||||
override fun getDescription(): String {
|
||||
return "searching for view $this in the root view"
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
var tries = 0
|
||||
val childViews: Iterable<View> = TreeIterables.breadthFirstViewTraversal(view)
|
||||
|
||||
// Look for the match in the tree of childviews
|
||||
childViews.forEach {
|
||||
tries++
|
||||
if (viewMatcher.matches(it)) {
|
||||
// found the view
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
throw NoMatchingViewException.Builder()
|
||||
.withRootView(view)
|
||||
.withViewMatcher(viewMatcher)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an action to check/uncheck a checkbox
|
||||
*
|
||||
*/
|
||||
fun setChecked(checked: Boolean): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): BaseMatcher<View> {
|
||||
return object : BaseMatcher<View>() {
|
||||
override fun describeTo(description: Description?) {}
|
||||
|
||||
override fun matches(actual: Any?): Boolean {
|
||||
return isA(CheckBox::class.java).matches(actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val checkableView = view as Checkable
|
||||
checkableView.isChecked = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action of implicitly waiting for a certain view.
|
||||
* This differs from EspressoExtensions.searchFor in that,
|
||||
* upon failure to locate an element, it will fetch a new root view
|
||||
* in which to traverse searching for our @param match
|
||||
*
|
||||
* @param viewMatcher ViewMatcher used to find our view
|
||||
*/
|
||||
fun waitForView(
|
||||
viewMatcher: Matcher<View>,
|
||||
waitMillis: Int = 5000,
|
||||
waitMillisPerTry: Long = 100
|
||||
): ViewInteraction {
|
||||
|
||||
// Derive the max tries
|
||||
val maxTries = waitMillis / waitMillisPerTry.toInt()
|
||||
|
||||
var tries = 0
|
||||
|
||||
for (i in 0..maxTries)
|
||||
try {
|
||||
// Track the amount of times we've tried
|
||||
tries++
|
||||
|
||||
// Search the root for the view
|
||||
onView(isRoot()).perform(searchFor(viewMatcher))
|
||||
|
||||
// If we're here, we found our view. Now return it
|
||||
return onView(viewMatcher)
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
if (tries == maxTries) {
|
||||
throw e
|
||||
}
|
||||
sleep(waitMillisPerTry)
|
||||
}
|
||||
|
||||
throw Exception("Error finding a view matching $viewMatcher")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.appttude.h_mal.atlas_weather.helpers
|
||||
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
import tools.fastlane.screengrab.Screengrab
|
||||
|
||||
/**
|
||||
* Junit rule that takes a screenshot when a test fails.
|
||||
*/
|
||||
class SnapshotRule : TestWatcher() {
|
||||
override fun failed(e: Throwable, description: Description) {
|
||||
// Catch a screenshot on failure
|
||||
Screengrab.screenshot("FAILURE-" + getScreenshotName(description))
|
||||
}
|
||||
|
||||
fun getScreenshotName(description: Description): String {
|
||||
return description.className.replace(".", "-") + "_" + description.methodName.replace(".", "-")
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,56 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
import android.content.Intent
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
|
||||
import com.appttude.h_mal.atlas_weather.helpers.SnapshotRule
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import tools.fastlane.screengrab.Screengrab
|
||||
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
|
||||
|
||||
open class BaseTest {
|
||||
open class BaseTest<A : Activity> {
|
||||
|
||||
lateinit var testApp: TestAppClass
|
||||
|
||||
@get:Rule
|
||||
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
|
||||
@get:Rule
|
||||
var snapshotRule: SnapshotRule = SnapshotRule()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mActivityTestRule: ActivityTestRule<MainActivity> = object : ActivityTestRule<MainActivity>(MainActivity::class.java) {
|
||||
override fun beforeActivityLaunched() {
|
||||
super.beforeActivityLaunched()
|
||||
var mActivityTestRule: ActivityTestRule<A> =
|
||||
object : ActivityTestRule<A>(getGenericClassAt<A>(0).java) {
|
||||
override fun beforeActivityLaunched() {
|
||||
super.beforeActivityLaunched()
|
||||
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
|
||||
|
||||
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||
setupFeed()
|
||||
testApp =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||
setupFeed()
|
||||
}
|
||||
|
||||
override fun afterActivityLaunched() {
|
||||
|
||||
// Dismiss dialog
|
||||
onView(withText("AGREE")).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
.perform(ViewActions.click())
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterActivityLaunched() {
|
||||
|
||||
// Dismiss dialog
|
||||
onView(withText("AGREE")).inRoot(isDialog()).check(matches(isDisplayed())).perform(ViewActions.click())
|
||||
}
|
||||
}
|
||||
|
||||
fun stubEndpoint(url: String, stub: Stubs) {
|
||||
testApp.stubUrl(url, stub.id)
|
||||
}
|
||||
@@ -45,7 +60,8 @@ open class BaseTest {
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {}
|
||||
fun tearDown() {
|
||||
}
|
||||
|
||||
open fun setupFeed() {}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ActivityScenario.launch
|
||||
@@ -14,7 +15,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -27,12 +28,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4ClassRunner::class)
|
||||
class HomePageUITestScenario : BaseMainScenario() {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mGrantPermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_COARSE_LOCATION")
|
||||
|
||||
override fun setupFeed() {
|
||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
||||
}
|
||||
@@ -49,12 +44,14 @@ class HomePageUITestScenario : BaseMainScenario() {
|
||||
open class BaseMainScenario {
|
||||
|
||||
lateinit var scenario: ActivityScenario<MainActivity>
|
||||
private lateinit var testApp : TestAppClass
|
||||
private lateinit var testApp: TestAppClass
|
||||
|
||||
@get:Rule
|
||||
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
scenario = launch(MainActivity::class.java)
|
||||
scenario.moveToState(Lifecycle.State.INITIALIZED)
|
||||
scenario.onActivity {
|
||||
runBlocking {
|
||||
testApp = it.application as TestAppClass
|
||||
@@ -62,12 +59,11 @@ open class BaseMainScenario {
|
||||
}
|
||||
}
|
||||
|
||||
scenario.moveToState(Lifecycle.State.CREATED).onActivity {
|
||||
Espresso.onView(ViewMatchers.withText("AGREE"))
|
||||
.inRoot(RootMatchers.isDialog())
|
||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
.perform(ViewActions.click())
|
||||
}
|
||||
// Dismiss dialog on start up
|
||||
Espresso.onView(ViewMatchers.withText("AGREE"))
|
||||
.inRoot(RootMatchers.isDialog())
|
||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
.perform(ViewActions.click())
|
||||
}
|
||||
|
||||
fun stubEndpoint(url: String, stub: Stubs) {
|
||||
@@ -79,7 +75,10 @@ open class BaseMainScenario {
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {}
|
||||
fun tearDown() {
|
||||
testFinished()
|
||||
}
|
||||
|
||||
open fun setupFeed() {}
|
||||
open fun testFinished() {}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ class WidgetLocationPermissionActivityTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
var mActivityTestRule : ActivityTestRule<WidgetLocationPermissionActivity> =
|
||||
ActivityTestRule<WidgetLocationPermissionActivity>(WidgetLocationPermissionActivity::class.java, false, false)
|
||||
ActivityTestRule(WidgetLocationPermissionActivity::class.java, false, false)
|
||||
|
||||
@Test
|
||||
fun demo_test() {
|
||||
val i = Intent()
|
||||
i.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 112)
|
||||
mActivityTestRule.launchActivity(i)
|
||||
val startIntent = Intent().apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 112)
|
||||
}
|
||||
mActivityTestRule.launchActivity(startIntent)
|
||||
|
||||
Espresso.onView((ViewMatchers.withId(R.id.declaration_text))).check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
@@ -1,51 +1,157 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.View
|
||||
import android.widget.DatePicker
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import androidx.test.espresso.Espresso.onData
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.PickerActions
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import com.appttude.h_mal.atlas_weather.checkErrorMessage
|
||||
import com.appttude.h_mal.atlas_weather.checkImage
|
||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.anything
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
open class BaseTestRobot {
|
||||
|
||||
fun fillEditText(resId: Int, text: String): ViewInteraction =
|
||||
onView(withId(resId)).perform(ViewActions.replaceText(text), ViewActions.closeSoftKeyboard())
|
||||
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
||||
onView(withId(resId)).perform(
|
||||
ViewActions.replaceText(text),
|
||||
ViewActions.closeSoftKeyboard()
|
||||
)
|
||||
|
||||
fun clickButton(resId: Int): ViewInteraction = onView((withId(resId))).perform(ViewActions.click())
|
||||
fun clickButton(resId: Int): ViewInteraction =
|
||||
onView((withId(resId))).perform(click())
|
||||
|
||||
fun textView(resId: Int): ViewInteraction = onView(withId(resId))
|
||||
fun matchView(resId: Int): ViewInteraction = onView(withId(resId))
|
||||
|
||||
fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId))
|
||||
|
||||
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
||||
.check(ViewAssertions.matches(ViewMatchers.withText(text)))
|
||||
.check(matches(withText(text)))
|
||||
|
||||
fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), text)
|
||||
fun matchText(viewId: Int, textId: Int): ViewInteraction = onView(withId(viewId))
|
||||
.check(matches(withText(textId)))
|
||||
|
||||
fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text)
|
||||
|
||||
fun clickListItem(listRes: Int, position: Int) {
|
||||
onData(anything())
|
||||
.inAdapterView(allOf(withId(listRes)))
|
||||
.atPosition(position).perform(ViewActions.click())
|
||||
.inAdapterView(allOf(withId(listRes)))
|
||||
.atPosition(position).perform(click())
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
||||
return matchView(recyclerId)
|
||||
.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.scrollTo<VH>(
|
||||
hasDescendant(withText(text))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> scrollToRecyclerItem(
|
||||
recyclerId: Int,
|
||||
resIdForString: Int
|
||||
): ViewInteraction? {
|
||||
return matchView(recyclerId)
|
||||
.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.scrollTo<VH>(
|
||||
hasDescendant(withText(resIdForString))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
||||
recyclerId: Int,
|
||||
position: Int
|
||||
): ViewInteraction? {
|
||||
return matchView(recyclerId)
|
||||
.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.scrollToPosition<VH>(position)
|
||||
)
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, text: String) {
|
||||
matchView(recyclerId)
|
||||
.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.actionOnItem<VH>(hasDescendant(withText(text)), click())
|
||||
)
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, resIdForString: Int) {
|
||||
matchView(recyclerId)
|
||||
.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.actionOnItem<VH>(
|
||||
hasDescendant(withText(resIdForString)),
|
||||
click()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun <VH : ViewHolder> clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) {
|
||||
scrollToRecyclerItem<VH>(recyclerId, text)
|
||||
?.perform(
|
||||
// scrollTo will fail the test if no item matches.
|
||||
RecyclerViewActions.actionOnItem<VH>(
|
||||
hasDescendant(withText(text)), object : ViewAction {
|
||||
override fun getDescription(): String = "Matching recycler descendant"
|
||||
override fun getConstraints(): Matcher<View>? = isRoot()
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
view?.findViewById<View>(subView)?.performClick()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
|
||||
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
|
||||
|
||||
fun checkImageViewHasImage(resId: Int): ViewInteraction =
|
||||
onView(withId(resId)).check(matches(checkImage()))
|
||||
|
||||
fun swipeDown(resId: Int): ViewInteraction =
|
||||
onView(withId(resId)).perform(swipeDown())
|
||||
|
||||
fun getStringFromResource(@StringRes resId: Int): String =
|
||||
Resources.getSystem().getString(resId)
|
||||
|
||||
fun pullToRefresh(resId: Int){
|
||||
onView(allOf(withId(resId), isDisplayed())).perform(swipeDown())
|
||||
onView(allOf(withId(resId), ViewMatchers.isDisplayed())).perform(swipeDown())
|
||||
}
|
||||
|
||||
fun waitFor(delay: Long): ViewAction? {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> = isRoot()
|
||||
override fun getDescription(): String = "wait for $delay milliseconds"
|
||||
override fun perform(uiController: UiController, v: View?) {
|
||||
uiController.loopMainThreadForAtLeast(delay)
|
||||
}
|
||||
}
|
||||
fun selectDateInPicker(year: Int, month: Int, day: Int) {
|
||||
onView(withClassName(equalTo(DatePicker::class.java.name))).perform(
|
||||
PickerActions.setDate(
|
||||
year,
|
||||
month,
|
||||
day
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.robot
|
||||
package com.appttude.h_mal.atlas_weather.robot
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.utils.BaseTestRobot
|
||||
@@ -1,18 +1,15 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
package com.appttude.h_mal.atlas_weather.tests
|
||||
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomePageUITest : BaseTest() {
|
||||
class HomePageUITest : BaseTest<MainActivity>() {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.appttude.h_mal.atlas_weather.robot
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.utils.BaseTestRobot
|
||||
|
||||
fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() }
|
||||
class HomeScreenRobot : BaseTestRobot() {
|
||||
fun verifyCurrentTemperature(temperature: Int) = matchText(R.id.temp_main_4, temperature.toString())
|
||||
fun verifyCurrentLocation(location: String) = matchText(R.id.location_main_4, location)
|
||||
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.appttude.h_mal.atlas_weather.tests
|
||||
|
||||
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomePageUITest : BaseTest<MainActivity>() {
|
||||
|
||||
override fun setupFeed() {
|
||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||
homeScreen {
|
||||
verifyCurrentTemperature(2)
|
||||
verifyCurrentLocation("Mock Location")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.appttude.h_mal.atlas_weather"
|
||||
tools:node="merge">
|
||||
|
||||
<application
|
||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
||||
@@ -12,10 +14,11 @@
|
||||
android:theme="@style/AppTheme"
|
||||
tools:node="merge">
|
||||
|
||||
<activity android:name=".ui.MainActivity"
|
||||
<activity android:name=".atlasWeather.ui.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@@ -25,14 +28,17 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.settings.UnitSettingsActivity"
|
||||
android:label="Settings" />
|
||||
android:name=".atlasWeather.ui.settings.UnitSettingsActivity"
|
||||
android:label="Settings"
|
||||
android:exported="true"/>
|
||||
|
||||
<receiver
|
||||
android:name=".notification.NotificationReceiver"
|
||||
android:parentActivityName=".MainActivity" />
|
||||
android:name=".atlasWeather.notification.NotificationReceiver"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:exported="true"/>
|
||||
|
||||
<receiver android:name=".widget.NewAppWidget">
|
||||
<receiver android:name=".atlasWeather.widget.NewAppWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
||||
@@ -46,7 +52,7 @@
|
||||
|
||||
|
||||
<service
|
||||
android:name=".widget.WidgetRemoteViewsService"
|
||||
android:name=".atlasWeather.widget.WidgetRemoteViewsService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
</application>
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import kotlinx.android.synthetic.atlasWeather.activity_main.*
|
||||
|
||||
class MainActivity : BaseActivity(){
|
||||
|
||||
lateinit var navHost: NavHostFragment
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
@@ -24,7 +26,7 @@ class MainActivity : BaseActivity(){
|
||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
val navHost = supportFragmentManager
|
||||
navHost = supportFragmentManager
|
||||
.findFragmentById(R.id.container) as NavHostFragment
|
||||
val navController = navHost.navController
|
||||
navController.setGraph(R.navigation.main_navigation)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -13,6 +14,7 @@ class WeatherRecyclerAdapter(
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var weather: WeatherDisplay? = null
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addCurrent(current: WeatherDisplay){
|
||||
weather = current
|
||||
notifyDataSetChanged()
|
||||
@@ -71,7 +73,6 @@ class WeatherRecyclerAdapter(
|
||||
when (getDataType(getItemViewType(position))){
|
||||
is ViewType.Empty -> {
|
||||
holder as EmptyViewHolder
|
||||
|
||||
}
|
||||
is ViewType.Current -> {
|
||||
val viewHolderCurrent = holder as ViewHolderCurrent
|
||||
|
||||
@@ -12,24 +12,24 @@ import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
interface WeatherDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertFullWeather(item: EntityItem)
|
||||
fun upsertFullWeather(item: EntityItem)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertListOfFullWeather(items: List<EntityItem>)
|
||||
fun upsertListOfFullWeather(items: List<EntityItem>)
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
||||
fun getCurrentFullWeather(userId: String) : LiveData<EntityItem>
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
||||
suspend fun getCurrentFullWeatherSingle(userId: String) : EntityItem
|
||||
fun getCurrentFullWeatherSingle(userId: String) : EntityItem
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
||||
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION) : LiveData<List<EntityItem>>
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
||||
suspend fun getWeatherListWithoutCurrent(id: String = CURRENT_LOCATION) : List<EntityItem>
|
||||
fun getWeatherListWithoutCurrent(id: String = CURRENT_LOCATION) : List<EntityItem>
|
||||
|
||||
@Query("DELETE FROM EntityItem WHERE id = :userId")
|
||||
suspend fun deleteEntry(userId: String): Int
|
||||
fun deleteEntry(userId: String): Int
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.appttude.h_mal.atlas_weather.helper
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object GenericsHelper {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
|
||||
((javaClass.genericSuperclass as? ParameterizedType)
|
||||
?.actualTypeArguments?.getOrNull(position) as? Class<CLASS>)
|
||||
?.kotlin
|
||||
?: throw IllegalStateException("Can not find class from generic argument")
|
||||
|
||||
// /**
|
||||
// * Create a view binding out of the the generic [VB]
|
||||
// *
|
||||
// * @sample inflateBindingByType(getGenericClassAt(0), layoutInflater)
|
||||
// */
|
||||
// fun <VB: ViewBinding> inflateBindingByType(
|
||||
// genericClassAt: KClass<VB>,
|
||||
// layoutInflater: LayoutInflater
|
||||
// ): VB = try {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
//
|
||||
// genericClassAt.java.methods.first { viewBinding ->
|
||||
// viewBinding.parameterTypes.size == 1
|
||||
// && viewBinding.parameterTypes.getOrNull(0) == LayoutInflater::class.java
|
||||
// }.invoke(null, layoutInflater) as VB
|
||||
// } catch (exception: Exception) {
|
||||
// println ("generic class failed at = $genericClassAt")
|
||||
// exception.printStackTrace()
|
||||
// throw IllegalStateException("Can not inflate binding from generic")
|
||||
// }
|
||||
//
|
||||
// fun <VB: ViewBinding> LayoutInflater.inflateBindingByType(
|
||||
// container: ViewGroup?,
|
||||
// genericClassAt: KClass<VB>
|
||||
// ): VB = try {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// genericClassAt.java.methods.first { inflateFun ->
|
||||
// inflateFun.parameterTypes.size == 3
|
||||
// && inflateFun.parameterTypes.getOrNull(0) == LayoutInflater::class.java
|
||||
// && inflateFun.parameterTypes.getOrNull(1) == ViewGroup::class.java
|
||||
// && inflateFun.parameterTypes.getOrNull(2) == Boolean::class.java
|
||||
// }.invoke(null, this, container, false) as VB
|
||||
// } catch (exception: Exception) {
|
||||
// throw IllegalStateException("Can not inflate binding from generic")
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user