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:
@@ -7,20 +7,11 @@ version: 2.1
|
|||||||
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
|
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
|
||||||
# See: https://circleci.com/docs/2.0/orb-intro/
|
# See: https://circleci.com/docs/2.0/orb-intro/
|
||||||
orbs:
|
orbs:
|
||||||
android: circleci/android@1.0.3
|
android: circleci/android@2.3.0
|
||||||
|
|
||||||
# Define a job to be invoked later in a workflow.
|
commands:
|
||||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
setup_repo:
|
||||||
jobs:
|
description: checkout repo and android dependencies
|
||||||
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
|
|
||||||
build-and-test:
|
|
||||||
# These next lines define the Android machine image executor.
|
|
||||||
# See: https://circleci.com/docs/2.0/executor-types/
|
|
||||||
executor:
|
|
||||||
name: android/android-machine
|
|
||||||
|
|
||||||
# Add steps to the job
|
|
||||||
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -35,19 +26,118 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- ~/.gradle
|
- ~/.gradle
|
||||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||||
|
run_tests:
|
||||||
|
description: run tests for flavour specified
|
||||||
|
parameters:
|
||||||
|
flavour:
|
||||||
|
type: string
|
||||||
|
default: "AtlasWeather"
|
||||||
|
steps:
|
||||||
|
# The next step will run the unit tests
|
||||||
|
- android/run-tests:
|
||||||
|
test-command: ./gradlew test<< parameters.flavour >>DebugUnitTest --continue
|
||||||
|
- store_artifacts:
|
||||||
|
path: app/build/reports
|
||||||
|
destination: reports
|
||||||
|
- store_test_results:
|
||||||
|
path: app/build/test-results
|
||||||
|
run_ui_tests:
|
||||||
|
description: run tests for flavour specified
|
||||||
|
parameters:
|
||||||
|
flavour:
|
||||||
|
type: string
|
||||||
|
default: "AtlasWeather"
|
||||||
|
steps:
|
||||||
|
- android/start-emulator-and-run-tests:
|
||||||
|
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
|
||||||
|
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
|
||||||
|
system-image: system-images;android-25;google_apis;x86
|
||||||
|
max-tries: 1
|
||||||
|
kill-emulators: false
|
||||||
|
- run:
|
||||||
|
name: Pull screenshots from device
|
||||||
|
command: |
|
||||||
|
mkdir ~/screenshots
|
||||||
|
adb pull /storage/emulated/0/Android/data/com.appttude.h_mal.atlas_weather/files/screengrab/en-US/images/screenshots ~/screenshots
|
||||||
|
when: on_fail
|
||||||
|
# store test reports
|
||||||
|
- store_artifacts:
|
||||||
|
path: app/build/reports/androidTests/connected
|
||||||
|
destination: reports
|
||||||
|
# store screenshots for failed ui tests
|
||||||
|
- store_artifacts:
|
||||||
|
path: ~/screenshots
|
||||||
|
destination: screenshots
|
||||||
|
|
||||||
|
# Define a job to be invoked later in a workflow.
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||||
|
jobs:
|
||||||
|
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
|
||||||
|
build-and-test:
|
||||||
|
# Parameters used for determining
|
||||||
|
parameters:
|
||||||
|
flavour:
|
||||||
|
type: string
|
||||||
|
default: "AtlasWeather"
|
||||||
|
# These next lines define the Android machine image executor.
|
||||||
|
# See: https://circleci.com/docs/2.0/executor-types/
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
tag: 2023.05.1
|
||||||
|
# Add steps to the job
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||||
|
steps:
|
||||||
|
- setup_repo
|
||||||
|
- run_tests:
|
||||||
|
flavour: << parameters.flavour >>
|
||||||
|
ui-test-and-release:
|
||||||
|
# Parameters used for determining
|
||||||
|
parameters:
|
||||||
|
flavour:
|
||||||
|
type: string
|
||||||
|
default: "AtlasWeather"
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
tag: 2023.05.1
|
||||||
|
steps:
|
||||||
|
- setup_repo
|
||||||
|
- run_ui_tests
|
||||||
- run:
|
- run:
|
||||||
name: Run Tests
|
name: Setup variables for release
|
||||||
command: ./gradlew lint test
|
command: |
|
||||||
- store_artifacts:
|
echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "android/app/release_keystore.jks"
|
||||||
path: app/build/reports
|
echo "$GOOGLE_PLAY_KEY" > "android/playstore.json"
|
||||||
destination: reports
|
# And finally run the release build
|
||||||
- store_test_results:
|
- run:
|
||||||
path: app/build/test-results
|
name: Assemble and Upload to PlayStore
|
||||||
|
command: |
|
||||||
|
pwd
|
||||||
|
bundle exec fastlane deploy<< parameters.flavour >>
|
||||||
|
|
||||||
# Invoke jobs via workflows
|
# Invoke jobs via workflows
|
||||||
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||||
workflows:
|
workflows:
|
||||||
sample: # This is the name of the workflow, feel free to change it to better match your workflow.
|
version: 2
|
||||||
# Inside the workflow, you define the jobs you want to run.
|
build-release-atlas:
|
||||||
jobs:
|
jobs:
|
||||||
- build-and-test
|
- build-and-test:
|
||||||
|
flavour: "AtlasWeather"
|
||||||
|
- ui-test-and-release:
|
||||||
|
flavour: "AtlasWeather"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- main_atlas
|
||||||
|
requires:
|
||||||
|
- build-and-test
|
||||||
|
build-release-mono:
|
||||||
|
jobs:
|
||||||
|
- build-and-test:
|
||||||
|
flavour: "MonoWeather"
|
||||||
|
- ui-test-and-release:
|
||||||
|
flavour: "MonoWeather"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: main_admin
|
||||||
|
requires:
|
||||||
|
- build-and-test
|
||||||
5
.idea/jarRepositories.xml
generated
5
.idea/jarRepositories.xml
generated
@@ -41,5 +41,10 @@
|
|||||||
<option name="name" value="maven" />
|
<option name="name" value="maven" />
|
||||||
<option name="url" value="https://maven.tomtom.com:8443/nexus/content/repositories/releases/" />
|
<option name="url" value="https://maven.tomtom.com:8443/nexus/content/repositories/releases/" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven" />
|
||||||
|
<option name="name" value="maven" />
|
||||||
|
<option name="url" value="https://repositories.tomtom.com/artifactory/maven" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
id 'com.android.application'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
id 'org.jetbrains.kotlin.android'
|
||||||
// kotlin kapt
|
id 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
// Android navigation
|
id 'androidx.navigation.safeargs'
|
||||||
apply plugin: 'androidx.navigation.safeargs'
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
compileSdkVersion 32
|
compileSdkVersion 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.appttude.h_mal.atlas_weather"
|
applicationId "com.appttude.h_mal.atlas_weather"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 32
|
targetSdkVersion 33
|
||||||
versionCode 5
|
versionCode 5
|
||||||
versionName "3.0"
|
versionName "3.0"
|
||||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||||
@@ -65,12 +64,16 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "default"
|
flavorDimensions "default"
|
||||||
productFlavors{
|
productFlavors {
|
||||||
atlasWeather{
|
atlasWeather {
|
||||||
applicationIdSuffix ".atlasWeather"
|
applicationId "com.appttude.h_mal.atlas_weather"
|
||||||
|
versionCode 5
|
||||||
|
versionName "3.0.0"
|
||||||
}
|
}
|
||||||
monoWeather{
|
monoWeather {
|
||||||
applicationIdSuffix ".monoWeather"
|
applicationId "com.appttude.h_mal.atlas_weather.monoWeather"
|
||||||
|
versionCode 5
|
||||||
|
versionName "3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -86,8 +89,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -115,16 +116,28 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
// android unit testing and espresso
|
// 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: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
|
//mock websever for testing retrofit responses
|
||||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
||||||
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
||||||
|
|
||||||
//mockito and livedata testing
|
//mockito and livedata testing
|
||||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||||
implementation 'android.arch.core:core-testing'
|
implementation 'androidx.arch.core:core-testing:2.2.0'
|
||||||
|
|
||||||
// Mockk
|
// Mockk
|
||||||
def mockk_ver = "1.10.5"
|
def mockk_ver = "1.10.5"
|
||||||
@@ -171,4 +184,6 @@ dependencies {
|
|||||||
/ * Glide */
|
/ * Glide */
|
||||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler: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 androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.Matcher.*
|
||||||
import org.hamcrest.Matchers.*
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -27,7 +26,8 @@ class LocationProviderImplTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
|
val appContext =
|
||||||
|
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
|
||||||
locationProvider = LocationProviderImpl(appContext)
|
locationProvider = LocationProviderImpl(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class LocationProviderImplTest {
|
|||||||
try {
|
try {
|
||||||
// Act
|
// Act
|
||||||
locationProvider.getLatLongFromLocationName(randomString)
|
locationProvider.getLatLongFromLocationName(randomString)
|
||||||
}catch (e: IOException){
|
} catch (e: IOException) {
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals(e.message, "No location found")
|
assertEquals(e.message, "No location found")
|
||||||
}
|
}
|
||||||
@@ -70,16 +70,14 @@ class LocationProviderImplTest {
|
|||||||
@Test
|
@Test
|
||||||
fun getLocationNameFromLatLong_locationTypeIsCity_correctLocationReturned() = runBlocking {
|
fun getLocationNameFromLatLong_locationTypeIsCity_correctLocationReturned() = runBlocking {
|
||||||
// Act
|
// Act
|
||||||
val retrievedLocation = locationProvider.getLocationNameFromLatLong(lat, long, LocationType.City)
|
val retrievedLocation =
|
||||||
|
locationProvider.getLocationNameFromLatLong(lat, long, LocationType.City)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals(retrievedLocation, city)
|
assertEquals(retrievedLocation, city)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertRangeOfDouble(input: Double, expected: Double, range: Double) {
|
private fun assertRangeOfDouble(input: Double, expected: Double, range: Double) {
|
||||||
assertThat(expected, allOf(
|
assertEquals(expected, input, range)
|
||||||
greaterThanOrEqualTo(input - range),
|
|
||||||
lessThanOrEqualTo(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
|
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.Espresso.onView
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.action.ViewActions.pressBack
|
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
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.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.rule.ActivityTestRule
|
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.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 com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Rule
|
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
|
lateinit var testApp: TestAppClass
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
var snapshotRule: SnapshotRule = SnapshotRule()
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
var mActivityTestRule: ActivityTestRule<MainActivity> = object : ActivityTestRule<MainActivity>(MainActivity::class.java) {
|
var mActivityTestRule: ActivityTestRule<A> =
|
||||||
override fun beforeActivityLaunched() {
|
object : ActivityTestRule<A>(getGenericClassAt<A>(0).java) {
|
||||||
super.beforeActivityLaunched()
|
override fun beforeActivityLaunched() {
|
||||||
|
super.beforeActivityLaunched()
|
||||||
|
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
|
||||||
|
|
||||||
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
testApp =
|
||||||
setupFeed()
|
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) {
|
fun stubEndpoint(url: String, stub: Stubs) {
|
||||||
testApp.stubUrl(url, stub.id)
|
testApp.stubUrl(url, stub.id)
|
||||||
}
|
}
|
||||||
@@ -45,7 +60,8 @@ open class BaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {}
|
fun tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
open fun setupFeed() {}
|
open fun setupFeed() {}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||||
|
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.core.app.ActivityScenario.launch
|
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.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||||
import androidx.test.rule.GrantPermissionRule
|
import androidx.test.rule.GrantPermissionRule
|
||||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
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.monoWeather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -27,12 +28,6 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4ClassRunner::class)
|
@RunWith(AndroidJUnit4ClassRunner::class)
|
||||||
class HomePageUITestScenario : BaseMainScenario() {
|
class HomePageUITestScenario : BaseMainScenario() {
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
var mGrantPermissionRule: GrantPermissionRule =
|
|
||||||
GrantPermissionRule.grant(
|
|
||||||
"android.permission.ACCESS_COARSE_LOCATION")
|
|
||||||
|
|
||||||
override fun setupFeed() {
|
override fun setupFeed() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
||||||
}
|
}
|
||||||
@@ -49,12 +44,14 @@ class HomePageUITestScenario : BaseMainScenario() {
|
|||||||
open class BaseMainScenario {
|
open class BaseMainScenario {
|
||||||
|
|
||||||
lateinit var scenario: ActivityScenario<MainActivity>
|
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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
scenario = launch(MainActivity::class.java)
|
scenario = launch(MainActivity::class.java)
|
||||||
scenario.moveToState(Lifecycle.State.INITIALIZED)
|
|
||||||
scenario.onActivity {
|
scenario.onActivity {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testApp = it.application as TestAppClass
|
testApp = it.application as TestAppClass
|
||||||
@@ -62,12 +59,11 @@ open class BaseMainScenario {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scenario.moveToState(Lifecycle.State.CREATED).onActivity {
|
// Dismiss dialog on start up
|
||||||
Espresso.onView(ViewMatchers.withText("AGREE"))
|
Espresso.onView(ViewMatchers.withText("AGREE"))
|
||||||
.inRoot(RootMatchers.isDialog())
|
.inRoot(RootMatchers.isDialog())
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
.perform(ViewActions.click())
|
.perform(ViewActions.click())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubEndpoint(url: String, stub: Stubs) {
|
fun stubEndpoint(url: String, stub: Stubs) {
|
||||||
@@ -79,7 +75,10 @@ open class BaseMainScenario {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {}
|
fun tearDown() {
|
||||||
|
testFinished()
|
||||||
|
}
|
||||||
|
|
||||||
open fun setupFeed() {}
|
open fun setupFeed() {}
|
||||||
|
open fun testFinished() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ class WidgetLocationPermissionActivityTest {
|
|||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
var mActivityTestRule : ActivityTestRule<WidgetLocationPermissionActivity> =
|
var mActivityTestRule : ActivityTestRule<WidgetLocationPermissionActivity> =
|
||||||
ActivityTestRule<WidgetLocationPermissionActivity>(WidgetLocationPermissionActivity::class.java, false, false)
|
ActivityTestRule(WidgetLocationPermissionActivity::class.java, false, false)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun demo_test() {
|
fun demo_test() {
|
||||||
val i = Intent()
|
val startIntent = Intent().apply {
|
||||||
i.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 112)
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 112)
|
||||||
mActivityTestRule.launchActivity(i)
|
}
|
||||||
|
mActivityTestRule.launchActivity(startIntent)
|
||||||
|
|
||||||
Espresso.onView((ViewMatchers.withId(R.id.declaration_text))).check(matches(isDisplayed()));
|
Espresso.onView((ViewMatchers.withId(R.id.declaration_text))).check(matches(isDisplayed()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,157 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.utils
|
package com.appttude.h_mal.atlas_weather.utils
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import android.view.View
|
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.onData
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.UiController
|
import androidx.test.espresso.UiController
|
||||||
import androidx.test.espresso.ViewAction
|
import androidx.test.espresso.ViewAction
|
||||||
import androidx.test.espresso.ViewInteraction
|
import androidx.test.espresso.ViewInteraction
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
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.*
|
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.allOf
|
||||||
import org.hamcrest.CoreMatchers.anything
|
import org.hamcrest.CoreMatchers.anything
|
||||||
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
open class BaseTestRobot {
|
open class BaseTestRobot {
|
||||||
|
|
||||||
fun fillEditText(resId: Int, text: String): ViewInteraction =
|
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
||||||
onView(withId(resId)).perform(ViewActions.replaceText(text), ViewActions.closeSoftKeyboard())
|
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
|
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) {
|
fun clickListItem(listRes: Int, position: Int) {
|
||||||
onData(anything())
|
onData(anything())
|
||||||
.inAdapterView(allOf(withId(listRes)))
|
.inAdapterView(allOf(withId(listRes)))
|
||||||
.atPosition(position).perform(ViewActions.click())
|
.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){
|
fun pullToRefresh(resId: Int){
|
||||||
onView(allOf(withId(resId), isDisplayed())).perform(swipeDown())
|
onView(allOf(withId(resId), ViewMatchers.isDisplayed())).perform(swipeDown())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun waitFor(delay: Long): ViewAction? {
|
fun selectDateInPicker(year: Int, month: Int, day: Int) {
|
||||||
return object : ViewAction {
|
onView(withClassName(equalTo(DatePicker::class.java.name))).perform(
|
||||||
override fun getConstraints(): Matcher<View> = isRoot()
|
PickerActions.setDate(
|
||||||
override fun getDescription(): String = "wait for $delay milliseconds"
|
year,
|
||||||
override fun perform(uiController: UiController, v: View?) {
|
month,
|
||||||
uiController.loopMainThreadForAtLeast(delay)
|
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.R
|
||||||
import com.appttude.h_mal.atlas_weather.utils.BaseTestRobot
|
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 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 com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageUITest : BaseTest() {
|
class HomePageUITest : BaseTest<MainActivity>() {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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
|
<application
|
||||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
||||||
@@ -12,10 +14,11 @@
|
|||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:node="merge">
|
tools:node="merge">
|
||||||
|
|
||||||
<activity android:name=".ui.MainActivity"
|
<activity android:name=".atlasWeather.ui.MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -25,14 +28,17 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.settings.UnitSettingsActivity"
|
android:name=".atlasWeather.ui.settings.UnitSettingsActivity"
|
||||||
android:label="Settings" />
|
android:label="Settings"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".notification.NotificationReceiver"
|
android:name=".atlasWeather.notification.NotificationReceiver"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
<receiver android:name=".widget.NewAppWidget">
|
<receiver android:name=".atlasWeather.widget.NewAppWidget"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
||||||
@@ -46,7 +52,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".widget.WidgetRemoteViewsService"
|
android:name=".atlasWeather.widget.WidgetRemoteViewsService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import kotlinx.android.synthetic.atlasWeather.activity_main.*
|
|||||||
|
|
||||||
class MainActivity : BaseActivity(){
|
class MainActivity : BaseActivity(){
|
||||||
|
|
||||||
|
lateinit var navHost: NavHostFragment
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
@@ -24,7 +26,7 @@ class MainActivity : BaseActivity(){
|
|||||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
val navHost = supportFragmentManager
|
navHost = supportFragmentManager
|
||||||
.findFragmentById(R.id.container) as NavHostFragment
|
.findFragmentById(R.id.container) as NavHostFragment
|
||||||
val navController = navHost.navController
|
val navController = navHost.navController
|
||||||
navController.setGraph(R.navigation.main_navigation)
|
navController.setGraph(R.navigation.main_navigation)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -13,6 +14,7 @@ class WeatherRecyclerAdapter(
|
|||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
var weather: WeatherDisplay? = null
|
var weather: WeatherDisplay? = null
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun addCurrent(current: WeatherDisplay){
|
fun addCurrent(current: WeatherDisplay){
|
||||||
weather = current
|
weather = current
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@@ -71,7 +73,6 @@ class WeatherRecyclerAdapter(
|
|||||||
when (getDataType(getItemViewType(position))){
|
when (getDataType(getItemViewType(position))){
|
||||||
is ViewType.Empty -> {
|
is ViewType.Empty -> {
|
||||||
holder as EmptyViewHolder
|
holder as EmptyViewHolder
|
||||||
|
|
||||||
}
|
}
|
||||||
is ViewType.Current -> {
|
is ViewType.Current -> {
|
||||||
val viewHolderCurrent = holder as ViewHolderCurrent
|
val viewHolderCurrent = holder as ViewHolderCurrent
|
||||||
|
|||||||
@@ -12,24 +12,24 @@ import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
|||||||
interface WeatherDao {
|
interface WeatherDao {
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun upsertFullWeather(item: EntityItem)
|
fun upsertFullWeather(item: EntityItem)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun upsertListOfFullWeather(items: List<EntityItem>)
|
fun upsertListOfFullWeather(items: List<EntityItem>)
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
||||||
fun getCurrentFullWeather(userId: String) : LiveData<EntityItem>
|
fun getCurrentFullWeather(userId: String) : LiveData<EntityItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
@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")
|
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
||||||
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION) : LiveData<List<EntityItem>>
|
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION) : LiveData<List<EntityItem>>
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
@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")
|
@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")
|
||||||
|
// }
|
||||||
|
}
|
||||||
34
build.gradle
34
build.gradle
@@ -1,36 +1,26 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
kotlin_version = '1.3.72'
|
kotlin_version = '1.5.20'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
maven { url "https://www.jitpack.io" }
|
||||||
jcenter()
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1")
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath ('com.android.tools.build:gradle:7.2.2')
|
||||||
|
classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
|
||||||
def nav_version = "2.3.0"
|
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
plugins {
|
||||||
repositories {
|
id 'com.android.application' version '7.2.2' apply false
|
||||||
jcenter()
|
id 'com.android.library' version '7.2.2' apply false
|
||||||
google()
|
id 'com.google.gms.google-services' version '4.3.15' apply false
|
||||||
maven {
|
id 'androidx.navigation.safeargs.kotlin' version '2.4.0' apply false
|
||||||
url 'https://maven.tomtom.com:8443/nexus/content/repositories/releases/'
|
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Fri Dec 07 02:26:23 AEST 2018
|
#Wed Jul 26 10:14:37 BST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
|
||||||
|
|||||||
@@ -1 +1,22 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://www.jitpack.io" }
|
||||||
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url "https://repositories.tomtom.com/artifactory/maps-sdk-legacy-android"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootProject.name = "Driver"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
|||||||
Reference in New Issue
Block a user