diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..e62e15f50 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: "https://github.com/AdevintaSpain/Barista/issues/344" diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 0b48fdce3..ae0c732db 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -8,7 +8,7 @@ about: Suggest an idea for this library **Description what you'd like to happen:** -A clear description if the feature or behavior you'd like implemented. +A clear description of the feature or behavior you'd like implemented. **Example espresso code if available** diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..e4c31c379 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,31 @@ +name: CI + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + tests: + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + name: Checkout + - uses: reactivecircus/android-emulator-runner@v2.11.0 + name: Run tests + with: + api-level: 28 + profile: Nexus 6 + headless: true + disable-animations: true + script: ./gradlew connectedCheck + + - uses: actions/cache@v1 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- diff --git a/.travis.yml b/.travis.yml index 127c805fc..7f9cb2ac1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,19 @@ env: global: - secure: "pus/Wv/014aNnUbUDNBpAk12o3w/zEO+/0oPomdnFs5034tsUOk53VbV69qKdWGKtH8Gm9LGBL50GLjSiB9Cxnc66BCPGj1ykBQ8RGGG/KHTDg22fuRVXmrSbEc+SqjwhpyUTpFjTf9RDmR2qlh427RbGofaNmkv7ePX14OHDjfVJW39jVNJv6+Jluyuh98zGpEbKq1SW+kNJsXUJMQjBtgyaQWN0qIjWflZWv4Rn97vxhyxFtRdGtJl1qCyhAcoIXNJyiYuabn/7sGsv4BjjmVBpFOU9npeJ/uMoXv8HQitjmV5wF9vq3VjmdPua9mrJwqG8BvREEA6r/Hj6vPpm0+qM62bDxpINZX3c9CjrvauaMeNSx3rfxK2yYfYO+f7cVaf0hsYS/pc9Pk0LeJy2FhycBt+WNlj/HSFu5Cvmh38kAj6Dm4HveF/CGFuhXYJAGr8IVH7aCPUeEC6JJLEoylNLglasitvNnfi/5f/smJWGf6e/XhFpAjlhoztRv6Hq7yRX3KZWWYmdl5blrsf6OWv7Z3R60IXfe4caPGMuBZNHToUyRmDfk89GXIJWTmVL4NLYSfO7fSaZ0YJ7nkDhoAgW/oRJuqEdbD404Sfzjp/o9YI6GoU9nxWD/RGRY/PGZ1pQs9djI6wny93kV/Y5W1h9BslEiKf1cxzj337M5s=" - secure: "pqbCHzhC2ePQt22kFqwA5RqQiJiQmlT8BEv5cPn84FVtGX0x1Qaw6NQ9k5ap2h/KGD0Axif5AdpruYRp4bicpscYOMLpk4fznXkNAX6U6MaQXR0ITgC2JTDtAZnAYuXo377uvdtHT0zPPfv/9mv5qY1bKdaCcRCSL8GX60ywf1YspJZNo7gC9yDZ6TiGTFTKycQqx1lPuPvtTopbp9oP6gDTnGhHDCrnn4Xi1NyyFnKJYVqaHSR25WgWAevNLnOA1Rbc7T67mrzzfeVv6wgS5+lJOuLCY1OgUwGrNmfqKVL67vUWYm5KuGFuHrV5pSiL6Y0CzJuI5gQl4H2h5tpadaEqOV5PswaWbqRW3aQGMsOKsLwv0oJIBcjE0NjkMx4Jusa7f1ofBkXWN2S8ozPDF8oVpJohzl8p2Fnhdrj+2f0eR+MBWFagWFwFeEKFGB1HDqF+XDQW21/Mar6nKc4tj2/wEtBcwfXL0Jo4c6O1GrUanyXLh4fXxIyuetoYIqwkG6hAvshXyoe0RP6UdifWiWnyEKu5Db6JV8IzjuR1QHCbcXjtXpl7Z6JodkRMw5bQzW9rrXTybRqK+ha+p3e/d1iynhjerxzavMadqSamWv02wQYD+vmeuf58SwZcy6XJJtJZ4M6yVL8msiVpsxpX/ln1jWHFEUKwJRRxKBkTHZo=" + +before_install: + - yes | sdkmanager "platforms;android-30" + android: components: - tools - tools - platform-tools - build-tools-28.0.3 - - android-29 + - android-30 - extra - - addon + - add-on licenses: - 'android-sdk-license-.+' diff --git a/README.md b/README.md index 1ac1ba324..44312d6f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Barista -**The guy who serves a great Espresso** +**The one who serves a great Espresso** -[![Travis](https://img.shields.io/travis/rust-lang/rust.svg?label=Travis+CI)](https://travis-ci.org/SchibstedSpain/Barista) +[![Travis](https://img.shields.io/travis/rust-lang/rust.svg?label=Travis+CI)](https://travis-ci.org/github/AdevintaSpain/Barista) [![Download](https://api.bintray.com/packages/schibstedspain/maven/barista/images/download.svg)](https://bintray.com/schibstedspain/maven/barista/_latestVersion) [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](LICENSE.md) @@ -27,8 +27,7 @@ Barista makes developing UI test faster, easier and more predictable. Built on t Import Barista as a testing dependency: ```gradle -androidTestImplementation('com.schibsted.spain:barista:3.2.0') { - exclude group: 'com.android.support' +androidTestImplementation('com.schibsted.spain:barista:3.7.0') { exclude group: 'org.jetbrains.kotlin' // Only if you already use Kotlin in your project } ``` @@ -67,6 +66,11 @@ longClickOn("Next"); clickMenu(R.id.menu_item); ``` +#### Open the overflow menu without clicking any item +```java +openMenu(); +``` + #### Writing into widgets ```java writeTo(R.id.edittext, "A great text"); @@ -80,13 +84,13 @@ clickListItem(R.id.list, 4); clickListItemChild(R.id.list, 3, R.id.row_button); doOnListItemChild(R.id.list, 5, R.id.edittext, replaceText("Yet another great text")); scrollListToPosition(R.id.list, 4); -assertListItemCount(R.id.listId, 5) -assertListNotEmpty(R.id.listId) -assertDisplayedAtPosition(R.id.recycler, 0, "text"); -assertDisplayedAtPosition(R.id.listId, 0, R.id.text_field, "text"); -assertDisplayedAtPosition(R.id.recycler, 0, R.string.hello_world); -assertDisplayedAtPosition(R.id.listId, 0, R.id.text_field, R.string.hello_world); -assertCustomAssertionAtPosition(R.id.recycler, 0, customViewAssertion); +assertListItemCount(R.id.list, 5) +assertListNotEmpty(R.id.list) +assertDisplayedAtPosition(R.id.list, 0, "text"); +assertDisplayedAtPosition(R.id.list, 0, R.id.text_field, "text"); +assertDisplayedAtPosition(R.id.list, 0, R.string.hello_world); +assertDisplayedAtPosition(R.id.list, 0, R.id.text_field, R.string.hello_world); +assertCustomAssertionAtPosition(R.id.list, 0, customViewAssertion); clickSpinnerItem(R.id.spinner, 1); ``` @@ -295,30 +299,31 @@ assertNotContains(R.id.textview, R.string.text); #### Check text is given color ```java -assertTextColorIs(R.id.someRedText, R.color.red); -assertTextColorIs(R.id.someColorListText, R.color.state_list); +assertTextColorIs(R.id.some_red_text, R.color.red); +assertTextColorIs(R.id.some_color_list_text, R.color.state_list); // ...or not? -assertTextColorIsNot(R.id.someRedText, R.color.blue); -assertTextColorIsNot(R.id.someColorListText, R.color.another_state_list); +assertTextColorIsNot(R.id.some_red_text, R.color.blue); +assertTextColorIsNot(R.id.some_color_list_text, R.color.another_state_list); ``` -#### Check recyclerView item count against expected item count -```java -assertRecyclerViewItemCount(R.id.recycler, 10); -``` +`assertTextColorIs` and its variant `assertTextColorIsNot` work with: -#### And another tricky feature -```java -assertThatBackButtonClosesTheApp(); -``` -#### Is this ImageView showing a drawable? +- *Color int*: `Color.parse("#ff00ff")` +- *Color resource*: `R.color.green` +- *Color attribute*: `R.attr.colorPrimary` + +Also Barista can check colors parsed from `declarable-style` custom attribute: ```java -assertHasAnyDrawable(R.id.image_view); -assertHasDrawable(R.id.image_view, R.drawable.ic_barista); +assertTextColorIs(R.id.customTextView, R.styleable.SampleCustomView, R.style.SampleCustomStyle, R.styleable.SampleCustomView_customColor); // ...or not? -assertHasNoDrawable(R.id.image_view); +assertTextColorIsNot(R.id.customTextView, R.styleable.SampleCustomView, R.style.SampleCustomStyle_Green, R.styleable.SampleCustomView_customColor); +``` + +#### Check recyclerView item count against expected item count +```java +assertRecyclerViewItemCount(R.id.recycler, 10); ``` #### Is this ProgressBar/SeekBar progress? @@ -328,6 +333,11 @@ assertProgressIsMin(R.id.seek_bar) assertProgressIsMax(R.id.seek_bar) ``` +#### And another tricky feature +```java +assertThatBackButtonClosesTheApp(); +``` + ### Custom assertions If you have a special case not covered by the given assertions API, we encourage you to assert these special cases with our custom assertions API. It's a convenient way to replace plain `Matcher`s with complex assertions. With Barista, you can match any kind of view by knowing its type and passing its `viewId`, `text`, or a `Matcher`. Once you matched it, you will be able to assert all its properties without adding any complex `Matcher` to your project. @@ -355,10 +365,17 @@ assertAny(R.id.radioGroup, "selected option is the second one") { } ``` -## Barista’s Intents API +## Mocking the Intent results + +Mocking the Android Camera Intent is a tricky thing to do. To accomplish it in no time, Barista gives a way to do it in one line: the method `mockAndroidCamera()`. This method does all the magic to mock the result of the camera. One more thing to do: you have to call `Intents.init()` before calling `mockAndroidCamera()`, and `Intents.release()` after doing the action that launches the camera. You could also use `IntentsTestRule` instead of the common `ActivityTestRule` to skip it, but as we recommend the use of `BaristaRule`, it's easier to just call both methods manually when needed. + +Here's an example to copy paste: + ```java -// Creates a Bitmap on a camera provided URI +Intents.init(); mockAndroidCamera(); +clickOn(R.id.launch_camera); +Intents.release(); ``` ## Runtime Permissions @@ -367,6 +384,9 @@ The new Marshmallow permissions system requires checking for permissions at runt ```java PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS); ``` +```java +PermissionGranter.allowPermissionOneTime(Manifest.permission.GET_ACCOUNTS); +``` ## Useful test rules Barista includes a set of useful test rules to help you: diff --git a/build.gradle b/build.gradle index 91951e051..97dc30a01 100644 --- a/build.gradle +++ b/build.gradle @@ -23,9 +23,9 @@ task clean(type: Delete) { } ext.minSdkVersionDeclared = 19 -ext.compileSdkVersionDeclared = 29 +ext.compileSdkVersionDeclared = 30 ext.supportLibVersion = '27.1.1' ext.espressoVersion = '3.0.2' ext.uiAutomatorVersion = '2.1.3' -ext.baristaVersion = '3.2.0' +ext.baristaVersion = '3.7.0' diff --git a/gradle.properties b/gradle.properties index 7ac1b74f2..af89d3512 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,2 @@ -android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m diff --git a/library/build.gradle b/library/build.gradle index 7d1725595..1e7a686c8 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -15,6 +15,7 @@ android { lintOptions { disable 'InvalidPackage' } + kotlinOptions.freeCompilerArgs += ["-module-name", "barista"] } dependencies { @@ -27,13 +28,16 @@ dependencies { implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' implementation 'androidx.vectordrawable:vectordrawable-animated:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0' + api 'androidx.viewpager2:viewpager2:1.0.0' + + implementation 'com.google.android.material:material:1.2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' testImplementation 'pl.pragmatists:JUnitParams:1.1.0' testImplementation 'org.assertj:assertj-core:1.7.0' - testImplementation 'org.mockito:mockito-core:2.13.0' + testImplementation 'org.mockito:mockito-core:2.28.2' } publish { diff --git a/library/src/androidTest/java/com/schibsted/spain/barista/internal/util/BitmapComparatorTest.kt b/library/src/androidTest/java/com/schibsted/spain/barista/internal/util/BitmapComparatorTest.kt index 9c7a066a2..ce97873a8 100644 --- a/library/src/androidTest/java/com/schibsted/spain/barista/internal/util/BitmapComparatorTest.kt +++ b/library/src/androidTest/java/com/schibsted/spain/barista/internal/util/BitmapComparatorTest.kt @@ -1,7 +1,8 @@ package com.schibsted.spain.barista.internal.util +import android.content.Context import android.graphics.BitmapFactory -import androidx.test.InstrumentationRegistry +import androidx.test.core.app.ApplicationProvider import com.schibsted.spain.barista.test.R import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -11,8 +12,8 @@ class BitmapComparatorTest { @Test fun returnTrueWhenComparingTheSameDrawable() { - val aBitmap = BitmapFactory.decodeResource(InstrumentationRegistry.getTargetContext().resources, R.drawable.ic_barista) - val theSameBitmap = BitmapFactory.decodeResource(InstrumentationRegistry.getTargetContext().resources, R.drawable.ic_barista) + val aBitmap = BitmapFactory.decodeResource(ApplicationProvider.getApplicationContext().resources, R.drawable.ic_barista) + val theSameBitmap = BitmapFactory.decodeResource(ApplicationProvider.getApplicationContext().resources, R.drawable.ic_barista) val result = BitmapComparator.compare(aBitmap, theSameBitmap) @@ -21,8 +22,8 @@ class BitmapComparatorTest { @Test fun returnFalseWhenComparingDifferentDrawables() { - val aBitmap = BitmapFactory.decodeResource(InstrumentationRegistry.getTargetContext().resources, R.drawable.ic_barista) - val aDifferentBitmap = BitmapFactory.decodeResource(InstrumentationRegistry.getTargetContext().resources, R.drawable.ic_launcher) + val aBitmap = BitmapFactory.decodeResource(ApplicationProvider.getApplicationContext().resources, R.drawable.ic_barista) + val aDifferentBitmap = BitmapFactory.decodeResource(ApplicationProvider.getApplicationContext().resources, R.drawable.ic_launcher) val result = BitmapComparator.compare(aBitmap, aDifferentBitmap) diff --git a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaChipAssertions.kt b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaChipAssertions.kt new file mode 100644 index 000000000..19c12eb37 --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaChipAssertions.kt @@ -0,0 +1,16 @@ +package com.schibsted.spain.barista.assertion + +import android.view.View +import androidx.annotation.IdRes +import com.google.android.material.chip.ChipGroup +import com.schibsted.spain.barista.assertion.BaristaAssertions.assertAny + +object BaristaChipAssertions { + + @JvmStatic + fun assertAnyChipSelected(@IdRes viewId: Int) { + assertAny(viewId) { + it.checkedChipId != View.NO_ID + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaErrorAssertions.kt b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaErrorAssertions.kt index b5778d395..0a18907c3 100644 --- a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaErrorAssertions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaErrorAssertions.kt @@ -1,12 +1,13 @@ package com.schibsted.spain.barista.assertion +import android.content.Context import androidx.annotation.IdRes import androidx.annotation.StringRes import com.google.android.material.textfield.TextInputLayout -import androidx.test.InstrumentationRegistry import androidx.test.espresso.matcher.ViewMatchers import android.view.View import android.widget.TextView +import androidx.test.core.app.ApplicationProvider import com.schibsted.spain.barista.internal.assertAny import org.hamcrest.Description import org.hamcrest.Matcher @@ -16,7 +17,7 @@ object BaristaErrorAssertions { @JvmStatic fun assertError(@IdRes viewId: Int, @StringRes text: Int) { - val resourceString = InstrumentationRegistry.getTargetContext().resources.getString(text) + val resourceString = ApplicationProvider.getApplicationContext().resources.getString(text) assertError(viewId, resourceString) } diff --git a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaHintAssertions.kt b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaHintAssertions.kt index 524c60fcf..cf7fe4252 100644 --- a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaHintAssertions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaHintAssertions.kt @@ -1,14 +1,15 @@ package com.schibsted.spain.barista.assertion +import android.content.Context import androidx.annotation.IdRes import androidx.annotation.StringRes import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout -import androidx.test.InstrumentationRegistry import androidx.test.espresso.matcher.ViewMatchers.withId import android.view.View import android.widget.EditText import android.widget.FrameLayout +import androidx.test.core.app.ApplicationProvider import com.schibsted.spain.barista.internal.assertAny import org.hamcrest.Description import org.hamcrest.Matcher @@ -18,7 +19,7 @@ object BaristaHintAssertions { @JvmStatic fun assertHint(@IdRes viewId: Int, @StringRes text: Int) { - val resourceString = InstrumentationRegistry.getTargetContext().resources.getString(text) + val resourceString = ApplicationProvider.getApplicationContext().resources.getString(text) assertHint(viewId, resourceString) } diff --git a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaVisibilityAssertions.kt b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaVisibilityAssertions.kt index 33ffc8181..6131b1d95 100644 --- a/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaVisibilityAssertions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/assertion/BaristaVisibilityAssertions.kt @@ -1,17 +1,20 @@ package com.schibsted.spain.barista.assertion -import androidx.annotation.ColorRes +import android.view.View +import androidx.annotation.AttrRes import androidx.annotation.IdRes import androidx.annotation.StringRes +import androidx.annotation.StyleRes +import androidx.annotation.StyleableRes import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText -import android.view.View import androidx.test.platform.app.InstrumentationRegistry import com.schibsted.spain.barista.internal.assertAny import com.schibsted.spain.barista.internal.matcher.TextColorMatcher +import com.schibsted.spain.barista.internal.matcher.TextStyleableColorMatcher import com.schibsted.spain.barista.internal.util.resourceMatcher import org.hamcrest.CoreMatchers.containsString import org.hamcrest.Matcher @@ -37,12 +40,12 @@ object BaristaVisibilityAssertions { @JvmStatic fun assertDisplayed(@IdRes viewId: Int, text: String) { - viewId.resourceMatcher().assertAny(withText(text)) + viewId.resourceMatcher().assertAny(allOf(isDisplayed(), withText(text))) } @JvmStatic fun assertDisplayed(@IdRes viewId: Int, @StringRes stringId: Int) { - viewId.resourceMatcher().assertAny(withText(stringId)) + viewId.resourceMatcher().assertAny(allOf(isDisplayed(), withText(stringId))) } @JvmStatic @@ -72,12 +75,12 @@ object BaristaVisibilityAssertions { @JvmStatic fun assertNotDisplayed(@IdRes viewId: Int, text: String) { - viewId.resourceMatcher().assertAny(not(withText(text))) + viewId.resourceMatcher().assertAny(not(allOf(isDisplayed(), withText(text)))) } @JvmStatic fun assertNotDisplayed(@IdRes viewId: Int, @StringRes stringId: Int) { - viewId.resourceMatcher().assertAny(not(withText(stringId))) + viewId.resourceMatcher().assertAny(not(allOf(isDisplayed(), withText(stringId)))) } @JvmStatic @@ -125,12 +128,38 @@ object BaristaVisibilityAssertions { } @JvmStatic - fun assertTextColorIs(@IdRes viewId: Int, @ColorRes colorRes: Int) { - viewId.resourceMatcher().assertAny(TextColorMatcher(colorRes)) + fun assertTextColorIs(@IdRes viewId: Int, color: Int) { + viewId.resourceMatcher().assertAny(TextColorMatcher(color)) + } + + @JvmStatic + fun assertTextColorIsNot(@IdRes viewId: Int, color: Int) { + viewId.resourceMatcher().assertAny(not(TextColorMatcher(color))) + } + + @JvmStatic + fun assertTextColorIs( + @IdRes viewId: Int, + @StyleableRes styleableRes: IntArray, + @StyleRes styleRes: Int, + @StyleableRes attrColor: Int + ) { + viewId.resourceMatcher().assertAny(TextStyleableColorMatcher( + styleableRes, styleRes, attrColor + )) } @JvmStatic - fun assertTextColorIsNot(@IdRes viewId: Int, @ColorRes colorRes: Int) { - viewId.resourceMatcher().assertAny(not(TextColorMatcher(colorRes))) + fun assertTextColorIsNot( + @IdRes viewId: Int, + @StyleableRes styleableRes: IntArray, + @StyleRes styleRes: Int, + @StyleableRes attrColor: Int + ) { + viewId.resourceMatcher().assertAny(not( + TextStyleableColorMatcher( + styleableRes, styleRes, attrColor + ) + )) } } diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaChipInteractions.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaChipInteractions.kt new file mode 100644 index 000000000..2d3f62f1f --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaChipInteractions.kt @@ -0,0 +1,13 @@ +package com.schibsted.spain.barista.interaction + +import com.schibsted.spain.barista.internal.performAction +import com.schibsted.spain.barista.internal.util.resourceMatcher +import com.schibsted.spain.barista.internal.viewaction.ChipViewActions.removeChip + +object BaristaChipInteractions { + + @JvmStatic + fun closeChip(resId: Int) { + resId.resourceMatcher().performAction(removeChip()) + } +} diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaListInteractions.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaListInteractions.kt index f4037f114..7f1f5ab4a 100644 --- a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaListInteractions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaListInteractions.kt @@ -1,7 +1,7 @@ package com.schibsted.spain.barista.interaction +import android.content.Context import androidx.annotation.IdRes -import androidx.test.InstrumentationRegistry import androidx.test.espresso.AmbiguousViewMatcherException import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onView @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import android.view.View import android.widget.AbsListView +import androidx.test.core.app.ApplicationProvider import com.schibsted.spain.barista.internal.failurehandler.SpyFailureHandler import com.schibsted.spain.barista.internal.failurehandler.description import com.schibsted.spain.barista.internal.failurehandler.withFailureHandler @@ -130,5 +131,5 @@ object BaristaListInteractions { else -> allOf(isDisplayed(), isAssignableFrom(AbsListView::class.java), withId(id)) } - private fun resourceName(resId: Int) = InstrumentationRegistry.getTargetContext().resources.getResourceName(resId) + private fun resourceName(resId: Int) = ApplicationProvider.getApplicationContext().resources.getResourceName(resId) } diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaMenuClickInteractions.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaMenuClickInteractions.kt index 814818388..b388075a8 100644 --- a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaMenuClickInteractions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaMenuClickInteractions.kt @@ -1,7 +1,7 @@ package com.schibsted.spain.barista.interaction +import android.content.Context import androidx.annotation.IdRes -import androidx.test.InstrumentationRegistry import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu @@ -12,6 +12,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import android.view.View +import androidx.test.core.app.ApplicationProvider import com.schibsted.spain.barista.internal.failurehandler.SpyFailureHandler import com.schibsted.spain.barista.internal.failurehandler.withFailureHandler import com.schibsted.spain.barista.internal.matcher.DisplayedMatchers.displayedAnd @@ -53,6 +54,11 @@ object BaristaMenuClickInteractions { } } + @JvmStatic + fun openMenu() { + openOverflow() + } + private fun clickDisplayedView(matcher: Matcher, spyFailureHandler: SpyFailureHandler) { onView(displayedAnd(matcher)).withFailureHandler(spyFailureHandler).perform(click()) } @@ -69,6 +75,6 @@ object BaristaMenuClickInteractions { } private fun openOverflow() { - openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) } } diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaScrollInteractions.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaScrollInteractions.kt index 267bdcfa2..3d59982aa 100644 --- a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaScrollInteractions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaScrollInteractions.kt @@ -1,9 +1,9 @@ package com.schibsted.spain.barista.interaction +import android.view.View import androidx.test.espresso.Espresso.onView import androidx.test.espresso.PerformException import androidx.test.espresso.matcher.ViewMatchers.withText -import android.view.View import com.schibsted.spain.barista.internal.failurehandler.SpyFailureHandler import com.schibsted.spain.barista.internal.failurehandler.description import com.schibsted.spain.barista.internal.util.resourceMatcher @@ -16,43 +16,53 @@ import org.hamcrest.Matcher */ object BaristaScrollInteractions { - // This value has been mathematically calculated and proven to be precisely the exact number of retries needed to always work. - // Not really, we just tried hundreds of times with different values and this seems to be the best one. - private val MAX_SCROLL_ATTEMPTS = 50 - - @JvmStatic - fun scrollTo(id: Int) { - scrollWithMultipleAttempts(id.resourceMatcher(), failAtEnd = true) - } - - @JvmStatic - fun scrollTo(text: String) { - scrollWithMultipleAttempts(withText(text), failAtEnd = true) - } - - @JvmStatic - fun safelyScrollTo(id: Int) { - scrollWithMultipleAttempts(id.resourceMatcher(), failAtEnd = false) - } - - @JvmStatic - fun safelyScrollTo(text: String) { - scrollWithMultipleAttempts(withText(text), failAtEnd = false) - } - - private fun scrollWithMultipleAttempts(matcher: Matcher, failAtEnd: Boolean) { - val spyFailureHandler = SpyFailureHandler() - for (i in 1..MAX_SCROLL_ATTEMPTS) { - try { - onView(matcher) - .withFailureHandler(spyFailureHandler) - .perform(nestedScrollToAction()) - } catch (exception: PerformException) { - if (i == MAX_SCROLL_ATTEMPTS && failAtEnd) { - spyFailureHandler - .resendLastError("Could not scroll to ${matcher.description()}. Retried ${MAX_SCROLL_ATTEMPTS} times but all failed") + // This value has been mathematically calculated and proven to be precisely the exact number of retries needed to always work. + // Not really, we just tried hundreds of times with different values and this seems to be the best one. + private val MAX_SCROLL_ATTEMPTS = 50 + + @JvmStatic + fun scrollTo(matcher: Matcher) { + scrollWithMultipleAttempts(matcher, failAtEnd = true) + } + + @JvmStatic + fun scrollTo(id: Int) { + scrollWithMultipleAttempts(id.resourceMatcher(), failAtEnd = true) + } + + @JvmStatic + fun scrollTo(text: String) { + scrollWithMultipleAttempts(withText(text), failAtEnd = true) + } + + @JvmStatic + fun safelyScrollTo(matcher: Matcher) { + scrollWithMultipleAttempts(matcher, failAtEnd = false) + } + + @JvmStatic + fun safelyScrollTo(id: Int) { + scrollWithMultipleAttempts(id.resourceMatcher(), failAtEnd = false) + } + + @JvmStatic + fun safelyScrollTo(text: String) { + scrollWithMultipleAttempts(withText(text), failAtEnd = false) + } + + private fun scrollWithMultipleAttempts(matcher: Matcher, failAtEnd: Boolean) { + val spyFailureHandler = SpyFailureHandler() + for (i in 1..MAX_SCROLL_ATTEMPTS) { + try { + onView(matcher) + .withFailureHandler(spyFailureHandler) + .perform(nestedScrollToAction()) + } catch (exception: PerformException) { + if (i == MAX_SCROLL_ATTEMPTS && failAtEnd) { + spyFailureHandler + .resendLastError("Could not scroll to ${matcher.description()}. Retried ${MAX_SCROLL_ATTEMPTS} times but all failed") + } + } } - } } - } } diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.java b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.java deleted file mode 100644 index 0d28fa7b1..000000000 --- a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.schibsted.spain.barista.interaction; - -import androidx.annotation.IdRes; -import androidx.viewpager.widget.ViewPager; - -import static androidx.test.espresso.Espresso.onView; -import static com.schibsted.spain.barista.internal.matcher.DisplayedMatchers.displayedAssignableFrom; -import static com.schibsted.spain.barista.internal.matcher.DisplayedMatchers.displayedWithId; -import static com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeLeft; -import static com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeRight; - -public class BaristaViewPagerInteractions { - - /* - * Finds a ViewPager and swipes it forward. - * - * It only works if there's only one ViewPager displayed on the screen. - * If there are more than one, use swipeViewPagerForward(@IdRes int id) - */ - public static void swipeViewPagerForward() { - onView(displayedAssignableFrom(ViewPager.class)).perform(swipeLeft()); - } - - public static void swipeViewPagerForward(@IdRes int id) { - onView(displayedWithId(id)).perform(swipeLeft()); - } - - /* - * Finds a ViewPager and swipes it back. - * - * It only works if there's only one ViewPager displayed on the screen. - * If there are more than one, use swipeViewPagerBack(@IdRes int id) - */ - public static void swipeViewPagerBack() { - onView(displayedAssignableFrom(ViewPager.class)).perform(swipeRight()); - } - - public static void swipeViewPagerBack(@IdRes int id) { - onView(displayedWithId(id)).perform(swipeRight()); - } -} diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.kt new file mode 100644 index 000000000..b18e181f0 --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/BaristaViewPagerInteractions.kt @@ -0,0 +1,85 @@ +package com.schibsted.spain.barista.interaction + +import androidx.annotation.IdRes +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 +import com.schibsted.spain.barista.internal.failurehandler.SpyFailureHandler +import com.schibsted.spain.barista.internal.matcher.DisplayedMatchers.displayedAssignableFrom +import com.schibsted.spain.barista.internal.matcher.DisplayedMatchers.displayedWithIdAndClass +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeLeft +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeRight +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeViewPager2Backward +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeViewPager2Forward + +object BaristaViewPagerInteractions { + + /* + * Finds a ViewPager and swipes it forward. + * + * It only works if there's only one ViewPager displayed on the screen. + * If there are more than one, use swipeViewPagerForward(@IdRes int id) + */ + @JvmStatic + fun swipeViewPagerForward() { + val spyFailureHandler = SpyFailureHandler() + try { + onView(displayedAssignableFrom(ViewPager::class.java)).perform(swipeLeft()) + } catch (noMatchingViewException: NoMatchingViewException) { + try { + onView(displayedAssignableFrom(ViewPager2::class.java)).perform(swipeViewPager2Forward()) + } catch (error: Exception) { + spyFailureHandler.resendFirstError("Could not swipe forward the ViewPager") + } + } + } + + @JvmStatic + fun swipeViewPagerForward(@IdRes id: Int) { + val spyFailureHandler = SpyFailureHandler() + try { + onView(displayedWithIdAndClass(id, ViewPager::class.java)).perform(swipeLeft()) + } catch (noMatchingViewException: NoMatchingViewException) { + try { + onView(displayedWithIdAndClass(id, ViewPager2::class.java)).perform(swipeViewPager2Forward()) + } catch (error: Exception) { + spyFailureHandler.resendFirstError("Could not swipe forward the ViewPager") + } + } + } + + /* + * Finds a ViewPager and swipes it back. + * + * It only works if there's only one ViewPager displayed on the screen. + * If there are more than one, use swipeViewPagerBack(@IdRes int id) + */ + @JvmStatic + fun swipeViewPagerBack() { + val spyFailureHandler = SpyFailureHandler() + try { + onView(displayedAssignableFrom(ViewPager::class.java)).perform(swipeRight()) + } catch (noMatchingViewException: NoMatchingViewException) { + try { + onView(displayedAssignableFrom(ViewPager2::class.java)).perform(swipeViewPager2Backward()) + } catch (error: Exception) { + spyFailureHandler.resendFirstError("Could not swipe back the ViewPager") + } + } + } + + @JvmStatic + fun swipeViewPagerBack(@IdRes id: Int) { + val spyFailureHandler = SpyFailureHandler() + try { + onView(displayedWithIdAndClass(id, ViewPager::class.java)).perform(swipeRight()) + } catch (noMatchingViewException: NoMatchingViewException) { + try { + onView(displayedWithIdAndClass(id, ViewPager2::class.java)).perform(swipeViewPager2Backward()) + } catch (error: Exception) { + spyFailureHandler.resendFirstError("Could not swipe back the ViewPager") + } + } + } +} diff --git a/library/src/main/java/com/schibsted/spain/barista/interaction/PermissionGranter.kt b/library/src/main/java/com/schibsted/spain/barista/interaction/PermissionGranter.kt index 7a4361d7e..35870faa6 100644 --- a/library/src/main/java/com/schibsted/spain/barista/interaction/PermissionGranter.kt +++ b/library/src/main/java/com/schibsted/spain/barista/interaction/PermissionGranter.kt @@ -14,24 +14,50 @@ import com.schibsted.spain.barista.interaction.BaristaSleepInteractions.sleepThr object PermissionGranter { private val PERMISSIONS_DIALOG_DELAY = 3000 - private val PERMISSIONS_DIALOG_ALLOW_ID = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - "com.android.permissioncontroller:id/permission_allow_button" - } else { + + private val PERMISSION_DIALOG_ALLOW_FOREGROUND_IDS = listOf( + "com.android.permissioncontroller:id/permission_allow_foreground_only_button", + "com.android.permissioncontroller:id/permission_allow_button", "com.android.packageinstaller:id/permission_allow_button" - } - // private static final String PERMISSIONS_DIALOG_DENY_ID = "com.android.packageinstaller:id/permission_deny_button"; + ) + + private val PERMISSION_DIALOG_ALLOW_ONE_TIME_IDS = listOf( + "com.android.permissioncontroller:id/permission_allow_one_time_button", + "com.android.permissioncontroller:id/permission_allow_button", + "com.android.packageinstaller:id/permission_allow_button" + ) + + private fun List.toPermissionButtonRegex() = joinToString( + prefix = "^(", + separator = "|", + postfix = ")$" + ) { it } @JvmStatic fun allowPermissionsIfNeeded(permissionNeeded: String) { + allowPermission(permissionNeeded, PERMISSION_DIALOG_ALLOW_FOREGROUND_IDS.toPermissionButtonRegex()) + } + + @JvmStatic + fun allowPermissionOneTime(permissionNeeded: String) { + allowPermission(permissionNeeded, PERMISSION_DIALOG_ALLOW_ONE_TIME_IDS.toPermissionButtonRegex()) + } + + private fun allowPermission(permissionNeeded: String, permissionsIds: String) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasNeededPermission(getApplicationContext(), - permissionNeeded)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasNeededPermission( + getApplicationContext(), + permissionNeeded + )) { sleepThread(PERMISSIONS_DIALOG_DELAY.toLong()) val device = UiDevice.getInstance(getInstrumentation()) - val allowPermissions = device.findObject(UiSelector() + + val allowPermissions = device.findObject( + UiSelector() .clickable(true) .checkable(false) - .resourceId(PERMISSIONS_DIALOG_ALLOW_ID)) + .resourceIdMatches(permissionsIds) + ) if (allowPermissions.exists()) { allowPermissions.click() } diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/BackgroundMatcher.kt b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/BackgroundMatcher.kt index d4f3cf010..722b353ba 100644 --- a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/BackgroundMatcher.kt +++ b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/BackgroundMatcher.kt @@ -1,5 +1,6 @@ package com.schibsted.spain.barista.internal.matcher +import android.graphics.drawable.ColorDrawable import androidx.annotation.DrawableRes import android.view.View import com.schibsted.spain.barista.internal.util.BitmapComparator @@ -44,9 +45,16 @@ class BackgroundMatcher private constructor(@DrawableRes private val expectedDra return false } - val viewBitmap = DrawableToBitmapConverter.getBitmap(view.background) - val expectedBitmap = DrawableToBitmapConverter.getBitmap(expectedDrawable) - return BitmapComparator.compare(viewBitmap, expectedBitmap) + if (expectedDrawable is ColorDrawable) { + val viewDrawable = view.background as ColorDrawable + return viewDrawable.color == expectedDrawable.color && + viewDrawable.alpha == expectedDrawable.alpha && + viewDrawable.opacity == expectedDrawable.opacity + } else { + val viewBitmap = DrawableToBitmapConverter.getBitmap(view.background) + val expectedBitmap = DrawableToBitmapConverter.getBitmap(expectedDrawable) + return BitmapComparator.compare(viewBitmap, expectedBitmap) + } } override fun describeTo(description: Description) { diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/DisplayedMatchers.java b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/DisplayedMatchers.java index 3dc81741e..25516348d 100644 --- a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/DisplayedMatchers.java +++ b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/DisplayedMatchers.java @@ -17,6 +17,11 @@ public static Matcher displayedWithId(@IdRes int id) { return allOf(isDisplayed(), withId(id)); } + @NonNull + public static Matcher displayedWithIdAndClass(@IdRes int id, final Class viewClass) { + return allOf(isDisplayed(), withId(id), isAssignableFrom(viewClass)); + } + @NonNull public static Matcher displayedAssignableFrom(final Class viewClass) { return allOf(isDisplayed(), isAssignableFrom(viewClass)); diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextColorMatcher.kt b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextColorMatcher.kt index 67af65c84..fe3627eb6 100644 --- a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextColorMatcher.kt +++ b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextColorMatcher.kt @@ -1,45 +1,105 @@ package com.schibsted.spain.barista.internal.matcher import android.content.Context -import androidx.annotation.ColorRes -import androidx.test.espresso.matcher.BoundedMatcher -import androidx.core.content.ContextCompat +import android.util.TypedValue import android.view.View import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.test.espresso.matcher.BoundedMatcher +import com.schibsted.spain.barista.internal.util.ColorResourceType +import com.schibsted.spain.barista.internal.util.colorResourceType import org.hamcrest.Description -class TextColorMatcher(@ColorRes private val colorRes: Int) : BoundedMatcher(TextView::class.java) { +class TextColorMatcher(private val expectedColor: Int) : BoundedMatcher(TextView::class.java) { private var colorName: String? = null override fun matchesSafely(textView: TextView): Boolean { - val context = textView.context - val resources = context.resources - colorName = resources.getResourceEntryName(colorRes) - val matchesColor = matchesColor(context, textView) - val matchesColorStateList = matchesColorList(context, textView) + colorName = when (expectedColor.colorResourceType) { + ColorResourceType.COLOR_RES -> textView.context.resources.getResourceEntryName(expectedColor) + ColorResourceType.COLOR_ATTR -> textView.context.resources.getResourceEntryName(expectedColor) + ColorResourceType.COLOR_INT -> "$expectedColor" + } - return matchesColor || matchesColorStateList + return matchesColor(textView) || (expectedColor.colorResourceType == ColorResourceType.COLOR_RES && matchesColorList(textView)) } - private fun matchesColor(context: Context, textView: TextView): Boolean { + private fun matchesColor(textView: TextView): Boolean { val currentColorInt = textView.currentTextColor - val expectedColorInt = ContextCompat.getColor(context, colorRes) + + val expectedColorInt = when (expectedColor.colorResourceType) { + ColorResourceType.COLOR_RES -> ContextCompat.getColor(textView.context, expectedColor) + ColorResourceType.COLOR_INT -> expectedColor + ColorResourceType.COLOR_ATTR -> textView.context.getColorAttribute(expectedColor) + } + return currentColorInt == expectedColorInt } - private fun matchesColorList(context: Context, textView: TextView): Boolean { + private fun Context.getColorAttribute(colorAttr: Int): Int { + val outValue = TypedValue() + theme.resolveAttribute(colorAttr, outValue, true) + return outValue.data + } + + private fun matchesColorList(textView: TextView): Boolean { val currentColorList = textView.textColors - val expectedColorList = ContextCompat.getColorStateList(context, colorRes) - return currentColorList == expectedColorList + val expectedColorList = ContextCompat.getColorStateList(textView.context, expectedColor)!! + + return getAllColorStateListStates().all { state -> + val currentStateColor = currentColorList.getColorForState(state, currentColorList.defaultColor) + val expectedStateColor = expectedColorList.getColorForState(state, expectedColorList.defaultColor) + currentStateColor == expectedStateColor + } + } + + private fun getAllColorStateListStates(): List { + return ALL_COLOR_STATE_LIST_STATES + .flatMap { state -> listOf(state, createDisabledState(state)) } + .map { state -> intArrayOf(state) } } override fun describeTo(description: Description) { - if (colorName == null) { - description.appendText("with text color resource: [$colorRes]") + if (colorName != null) { + val text = when (expectedColor.colorResourceType) { + ColorResourceType.COLOR_RES -> "with text color resource: [$colorName]" + ColorResourceType.COLOR_INT -> "with text color: [$expectedColor]" + ColorResourceType.COLOR_ATTR -> "with text color attribute: [$colorName]" + } + + description.appendText(text) } else { - description.appendText("with text color: [$colorName]") + description.appendText("with text color: [$expectedColor]") } } + + /** + * To create the oposite value of a given state is needed to "-" sign before the state + * + * @see Check documentation on StackOverflow + */ + private fun createDisabledState(state: Int) = -state + + companion object { + private val ALL_COLOR_STATE_LIST_STATES = listOf( + android.R.attr.state_focused, + android.R.attr.state_window_focused, + android.R.attr.state_enabled, + android.R.attr.state_checkable, + android.R.attr.state_checked, + android.R.attr.state_selected, + android.R.attr.state_pressed, + android.R.attr.state_activated, + android.R.attr.state_active, + android.R.attr.state_single, + android.R.attr.state_first, + android.R.attr.state_middle, + android.R.attr.state_last, + android.R.attr.state_accelerated, + android.R.attr.state_hovered, + android.R.attr.state_drag_can_accept, + android.R.attr.state_drag_hovered + ) + } } \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextStyleableColorMatcher.kt b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextStyleableColorMatcher.kt new file mode 100644 index 000000000..904131f41 --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/internal/matcher/TextStyleableColorMatcher.kt @@ -0,0 +1,70 @@ +package com.schibsted.spain.barista.internal.matcher +import android.content.res.ColorStateList +import android.view.View +import android.widget.TextView +import androidx.annotation.AttrRes +import androidx.annotation.StyleRes +import androidx.annotation.StyleableRes +import androidx.test.espresso.matcher.BoundedMatcher +import com.schibsted.spain.barista.internal.util.ColorResourceType +import com.schibsted.spain.barista.internal.util.colorResourceType +import org.hamcrest.Description + +class TextStyleableColorMatcher( + @StyleableRes private val styleableRes: IntArray, + @StyleRes private val styleRes: Int, + @StyleableRes private val attrColor: Int +) : BoundedMatcher(TextView::class.java) { + + private var styleResName: String? = null + + override fun matchesSafely(textView: TextView): Boolean { + styleResName = textView.context.resources.getResourceEntryName(styleRes) + + return matchesColor(textView) || matchesColorList(textView) + } + + private fun matchesColor(textView: TextView): Boolean { + val currentColorInt = textView.currentTextColor + val expectedColorInt = getStyleableColor(textView) + return currentColorInt == expectedColorInt + } + + private fun matchesColorList(textView: TextView): Boolean { + val currentColorList = textView.textColors + val expectedColorList = getStyleableColorList(textView) + return currentColorList == expectedColorList + } + + private fun getStyleableColor(textView: TextView): Int { + val typedArray = textView.context.obtainStyledAttributes( + null, + styleableRes, + 0, + styleRes + ) + + val expectedColorInt = typedArray.getColor(attrColor, -1) + + typedArray.recycle() + return expectedColorInt + } + + private fun getStyleableColorList(textView: TextView): ColorStateList? { + val typedArray = textView.context.obtainStyledAttributes( + null, + styleableRes, + 0, + styleRes + ) + + val expectedColorInt = typedArray.getColorStateList(attrColor) + + typedArray.recycle() + return expectedColorInt + } + + override fun describeTo(description: Description) { + description.appendText("with style matching [$styleResName]") + } +} \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/util/ResourceType.kt b/library/src/main/java/com/schibsted/spain/barista/internal/util/ResourceType.kt index 68290c7ff..364174759 100644 --- a/library/src/main/java/com/schibsted/spain/barista/internal/util/ResourceType.kt +++ b/library/src/main/java/com/schibsted/spain/barista/internal/util/ResourceType.kt @@ -1,9 +1,11 @@ package com.schibsted.spain.barista.internal.util -import androidx.test.InstrumentationRegistry +import android.content.Context +import android.content.res.Resources +import android.view.View +import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText -import android.view.View import org.hamcrest.Matcher class BaristaResourceTypeException(message: String) : RuntimeException(message) @@ -14,7 +16,7 @@ enum class ResourceType { val Int.resourceType: ResourceType get() { - val resourceTypeName = InstrumentationRegistry.getTargetContext().resources.getResourceTypeName(this) + val resourceTypeName = ApplicationProvider.getApplicationContext().resources.getResourceTypeName(this) return when (resourceTypeName) { "id" -> ResourceType.ID "string" -> ResourceType.STRING @@ -26,3 +28,20 @@ fun Int.resourceMatcher(): Matcher = when (resourceType) { ResourceType.ID -> withId(this) ResourceType.STRING -> withText(this) } + +enum class ColorResourceType { + COLOR_RES, COLOR_INT, COLOR_ATTR +} + +val Int.colorResourceType: ColorResourceType + get() { + return try { + when (val resourceTypeName = ApplicationProvider.getApplicationContext().resources.getResourceTypeName(this)) { + "color" -> ColorResourceType.COLOR_RES + "attr" -> ColorResourceType.COLOR_ATTR + else -> throw BaristaResourceTypeException("The argument must be ColorInt or R.color.* or R.attr.*, but was $resourceTypeName") + } + } catch (e: Resources.NotFoundException) { + ColorResourceType.COLOR_INT + } + } \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/ChipViewActions.kt b/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/ChipViewActions.kt new file mode 100644 index 000000000..d4e28cc9c --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/ChipViewActions.kt @@ -0,0 +1,29 @@ +package com.schibsted.spain.barista.internal.viewaction + +import android.view.View +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import com.google.android.material.chip.Chip +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf + +object ChipViewActions { + + @JvmStatic + fun removeChip(): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher { + return allOf(isDisplayed(), isAssignableFrom(Chip::class.java)) + } + + override fun perform(uiController: UiController, view: View) { + val chip = view as Chip + chip.performCloseIconClick() + } + + override fun getDescription(): String = "Close chip" + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/SwipeActions.kt b/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/SwipeActions.kt index b3016e695..fed0133bc 100644 --- a/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/SwipeActions.kt +++ b/library/src/main/java/com/schibsted/spain/barista/internal/viewaction/SwipeActions.kt @@ -1,5 +1,8 @@ package com.schibsted.spain.barista.internal.viewaction +import android.view.View +import android.view.ViewConfiguration +import androidx.core.view.ViewCompat import androidx.test.espresso.PerformException import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction @@ -9,10 +12,19 @@ import androidx.test.espresso.action.Press import androidx.test.espresso.action.Swipe import androidx.test.espresso.action.Swiper import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.swipeDown +import androidx.test.espresso.action.ViewActions.swipeUp import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA import androidx.test.espresso.util.HumanReadables -import android.view.View -import android.view.ViewConfiguration +import androidx.viewpager2.widget.ViewPager2 +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeLeft +import com.schibsted.spain.barista.internal.viewaction.SwipeActions.swipeRight +import com.schibsted.spain.barista.internal.viewaction.ViewPager2SwipeAction.Direction.BACKWARD +import com.schibsted.spain.barista.internal.viewaction.ViewPager2SwipeAction.Direction.FORWARD +import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.CoreMatchers.anyOf import org.hamcrest.Matcher object SwipeActions { @@ -43,6 +55,12 @@ object SwipeActions { NormalizedLocation(from.first, from.second), NormalizedLocation(to.first, to.second), Press.FINGER)) + + @JvmStatic + fun swipeViewPager2Forward(): ViewAction = ViewPager2SwipeAction(direction = FORWARD) + + @JvmStatic + fun swipeViewPager2Backward(): ViewAction = ViewPager2SwipeAction(direction = BACKWARD) } private class NormalizedLocation(val normalizedXPosition: Float, val normalizedYPosition: Float) : CoordinatesProvider { @@ -131,4 +149,47 @@ private class GeneralSwipeWithPartiallyVisibleViewAction( /** Maximum number of times to attempt sending a swipe action. */ private const val MAX_TRIES = 3 } -} \ No newline at end of file +} + +/** + * This class will find a [ViewPager2] and perform the correct swipe given [direction] based on the + * current [ViewPager2.Orientation] and the right-to-left layout direction. + */ +private class ViewPager2SwipeAction(private val direction: Direction) : ViewAction { + + enum class Direction { + FORWARD, + BACKWARD + } + + override fun getDescription(): String = "Swiping ViewPager2 $direction" + + override fun getConstraints(): Matcher = allOf(anyOf( + isAssignableFrom(ViewPager2::class.java), + isDescendantOfA(isAssignableFrom(ViewPager2::class.java)) + )) + + override fun perform(uiController: UiController, view: View) { + val viewPager = if (view is ViewPager2) { + view + } else { + var parent = view.parent + while (parent !is ViewPager2 && parent != null) { + parent = parent.parent + } + parent as ViewPager2 + } + + val isForward = direction == FORWARD + val swipeAction: ViewAction = if (viewPager.orientation == ViewPager2.ORIENTATION_HORIZONTAL) { + if (isForward == viewPager.isRtl()) swipeRight() else swipeLeft() + } else { + if (isForward) swipeUp() else swipeDown() + } + swipeAction.perform(uiController, view) + } + + private fun ViewPager2.isRtl(): Boolean { + return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL + } +} diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearDatabaseRule.kt b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearDatabaseRule.kt index 17860f75d..8273becdf 100644 --- a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearDatabaseRule.kt +++ b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearDatabaseRule.kt @@ -14,6 +14,7 @@ class ClearDatabaseRule(private val databaseOperations: DatabaseOperations = Dat companion object { @JvmField internal val UNWANTED_FILENAME_SUFFIXES = arrayOf("-journal", "-shm", "-uid", "-wal") + const val ROOM_METADATA = "room_master_table" } private var excludeTablesRegex: Regex? = null @@ -42,6 +43,7 @@ class ClearDatabaseRule(private val databaseOperations: DatabaseOperations = Dat .use { database -> getTableNames(database) .filterNot { excludeTablesRegex?.matches(it) ?: false } + .filterNot { it == ROOM_METADATA } .forEach { tableName -> deleteTableContent(database, tableName) } diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearPreferencesRule.java b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearPreferencesRule.java index 80db41562..9a46dbd05 100644 --- a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearPreferencesRule.java +++ b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/ClearPreferencesRule.java @@ -3,7 +3,8 @@ import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.NonNull; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -38,7 +39,7 @@ private void clearData() { @NonNull private List getAllPreferencesFiles() { - Context context = InstrumentationRegistry.getTargetContext().getApplicationContext(); + Context context = ApplicationProvider.getApplicationContext(); String rootPath = context.getApplicationInfo().dataDir + "/shared_prefs"; File prefsFolder = new File(rootPath); diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/DatabaseOperations.kt b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/DatabaseOperations.kt index 8b0923742..d14c7b003 100644 --- a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/DatabaseOperations.kt +++ b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/DatabaseOperations.kt @@ -1,14 +1,14 @@ package com.schibsted.spain.barista.rule.cleardata.internal import android.database.sqlite.SQLiteDatabase -import androidx.test.InstrumentationRegistry +import androidx.test.platform.app.InstrumentationRegistry import java.io.File import java.util.ArrayList -class DatabaseOperations { +open class DatabaseOperations { - fun getAllDatabaseFiles(): List { - return InstrumentationRegistry.getTargetContext() + open fun getAllDatabaseFiles(): List { + return InstrumentationRegistry.getInstrumentation().targetContext .let { context -> context.databaseList() .map { context.getDatabasePath(it) } @@ -16,14 +16,14 @@ class DatabaseOperations { } } - fun openDatabase(databaseFile: File): SQLiteDatabase { + open fun openDatabase(databaseFile: File): SQLiteDatabase { return SQLiteDatabase.openDatabase( databaseFile.absolutePath, null /* cursorFactory */, 0 /* flags */) } - fun getTableNames(sqLiteDatabase: SQLiteDatabase): List { + open fun getTableNames(sqLiteDatabase: SQLiteDatabase): List { sqLiteDatabase.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)", arrayOf("table", "view")) .use { cursor -> val tableNames = ArrayList() @@ -34,7 +34,7 @@ class DatabaseOperations { } } - fun deleteTableContent(sqLiteDatabase: SQLiteDatabase, tableName: String) { + open fun deleteTableContent(sqLiteDatabase: SQLiteDatabase, tableName: String) { sqLiteDatabase.delete(tableName, null, null) } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/FileOperations.kt b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/FileOperations.kt index 3d07a3d66..beb74104f 100644 --- a/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/FileOperations.kt +++ b/library/src/main/java/com/schibsted/spain/barista/rule/cleardata/internal/FileOperations.kt @@ -1,11 +1,12 @@ package com.schibsted.spain.barista.rule.cleardata.internal -import androidx.test.InstrumentationRegistry.getTargetContext +import android.content.Context +import androidx.test.core.app.ApplicationProvider import java.io.File class FileOperations { - private val appContext = getTargetContext() + private val appContext = ApplicationProvider.getApplicationContext() private val androidDirectories = arrayOf(appContext.cacheDir, appContext.filesDir) fun deleteFile(file: File) { diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightRule.kt b/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightRule.kt new file mode 100644 index 000000000..a09bee859 --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightRule.kt @@ -0,0 +1,20 @@ +package com.schibsted.spain.barista.rule.theme + +import androidx.appcompat.app.AppCompatDelegate +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class DayNightRule(private val modes: List = DAY_NIGHT_MODES) : TestRule { + + override fun apply(base: Statement, description: Description): Statement { + return DayNightStatement(base, description, modes) + } + + companion object { + private val DAY_NIGHT_MODES: List = listOf( + AppCompatDelegate.MODE_NIGHT_NO, + AppCompatDelegate.MODE_NIGHT_YES + ) + } +} \ No newline at end of file diff --git a/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightStatement.kt b/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightStatement.kt new file mode 100644 index 000000000..173a7e1dd --- /dev/null +++ b/library/src/main/java/com/schibsted/spain/barista/rule/theme/DayNightStatement.kt @@ -0,0 +1,43 @@ +package com.schibsted.spain.barista.rule.theme + +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.appcompat.app.AppCompatDelegate +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class DayNightStatement( + private val base: Statement, + private val description: Description, + private val modes: List +) : Statement() { + + override fun evaluate() { + modes.forEach { mode -> + Handler(Looper.getMainLooper()).post { + AppCompatDelegate.setDefaultNightMode(mode) + } + try { + base.evaluate() + } catch (throwable: Throwable) { + val testName = "${description.testClass.simpleName}\$${description.methodName}" + val errorMessage = "Test $testName failed on DayNight mode: [${getDayNightModeName(mode)}]" + Log.e(TAG, errorMessage) + throw throwable + } + } + } + + private fun getDayNightModeName(mode: Int): String = when (mode) { + AppCompatDelegate.MODE_NIGHT_AUTO -> "MODE_NIGHT_AUTO" + AppCompatDelegate.MODE_NIGHT_NO -> "MODE_NIGHT_NO" + AppCompatDelegate.MODE_NIGHT_YES -> "MODE_NIGHT_YES" + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> "MODE_NIGHT_FOLLOW_SYSTEM" + else -> "Unknown" + } + + companion object { + private const val TAG = "DAY_NIGHT" + } +} \ No newline at end of file diff --git a/library/src/test/java/com/schibsted/spain/barista/cleardata/ClearDatabaseRuleTest.java b/library/src/test/java/com/schibsted/spain/barista/cleardata/ClearDatabaseRuleTest.java index fbc47f1e3..2c2df2906 100644 --- a/library/src/test/java/com/schibsted/spain/barista/cleardata/ClearDatabaseRuleTest.java +++ b/library/src/test/java/com/schibsted/spain/barista/cleardata/ClearDatabaseRuleTest.java @@ -117,6 +117,19 @@ public void doesNotDeleteTable_whenNameMatchesRegex() throws Throwable { verify(operations, atLeastOnce()).deleteTableContent(DB_1, "some_table"); } + @Test + public void doesNotDeleteRoomMetadata() throws Throwable { + String givenTableName = "some_table"; + given(operations.getAllDatabaseFiles()).willReturn(singletonList(DB_1_FILE)); + given(operations.getTableNames(DB_1)).willReturn(asList(givenTableName, ClearDatabaseRule.ROOM_METADATA)); + + ClearDatabaseRule rule = new ClearDatabaseRule(operations); + executeRule(rule); + + verify(operations, never()).deleteTableContent(DB_1, ClearDatabaseRule.ROOM_METADATA); + verify(operations, atLeastOnce()).deleteTableContent(DB_1, givenTableName); + } + private void executeRule(ClearDatabaseRule rule) throws Throwable { rule.apply(dummyStatement, dummyDescription).evaluate(); } diff --git a/sample/build.gradle b/sample/build.gradle index 7ae4df582..faf669220 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -30,14 +30,16 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.2.0' implementation 'androidx.annotation:annotation:1.0.2' - implementation 'com.github.bumptech.glide:glide:4.7.1' + implementation 'com.github.bumptech.glide:glide:4.10.0' + implementation 'com.google.android.material:material:1.2.0' + implementation 'androidx.core:core-ktx:1.0.1' androidTestImplementation project(':library') androidTestImplementation "org.assertj:assertj-core:2.9.1" androidTestImplementation "com.nhaarman:mockito-kotlin:1.5.0" - androidTestImplementation "org.mockito:mockito-android:2.16.0" + androidTestImplementation "org.mockito:mockito-android:2.28.2" testImplementation 'junit:junit:4.12' diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/AssertionsTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/AssertionsTest.java index 0bf8c79eb..40ba9db48 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/AssertionsTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/AssertionsTest.java @@ -259,6 +259,16 @@ public void checkDrawable_withId() throws Exception { assertHasDrawable(R.id.image_view, R.drawable.ic_barista); } + @Test + public void checkVectorDrawable_withId() throws Exception { + assertHasDrawable(R.id.vector_image_view, R.drawable.barista_logo_vector); + } + + @Test + public void checkVectorDrawableFromCode_withId() throws Exception { + assertHasDrawable(R.id.code_vector_image_view, R.drawable.barista_logo_vector); + } + @Test(expected = BaristaException.class) public void checkDrawable_withId_failure() throws Exception { assertHasDrawable(R.id.image_view, R.drawable.ic_action_menu); @@ -269,6 +279,16 @@ public void checkDrawable_withAnyDrawable() throws Exception { assertHasAnyDrawable(R.id.image_view); } + @Test + public void checkVectorDrawable_withAnyDrawable() throws Exception { + assertHasAnyDrawable(R.id.vector_image_view); + } + + @Test + public void checkVectorDrawableFromCode_withAnyDrawable() throws Exception { + assertHasAnyDrawable(R.id.code_vector_image_view); + } + @Test(expected = BaristaException.class) public void checkDrawable_withAnyDrawable_failure() throws Exception { assertHasAnyDrawable(R.id.image_view_without_image); @@ -285,18 +305,28 @@ public void checkDrawable_withoutDrawable_failure() throws Exception { } @Test - public void checkBackground_withId() throws Exception { - assertHasBackground(R.id.view_with_backbround, R.drawable.ic_barista); + public void checkBackgroundDrawable_withId() throws Exception { + assertHasBackground(R.id.view_with_drawable_backbround, R.drawable.ic_barista); + } + + @Test + public void checkBackgroundColor_withId() throws Exception { + assertHasBackground(R.id.view_with_color_backbround, R.color.red); } @Test(expected = BaristaException.class) - public void checkBackground_withId_failure() throws Exception { + public void checkBackgroundDrawable_withId_failure() throws Exception { assertHasBackground(R.id.view_without_backbround, R.drawable.ic_action_menu); } + @Test(expected = BaristaException.class) + public void checkBackgroundColor_withId_failure() throws Exception { + assertHasBackground(R.id.view_with_color_backbround, R.color.blue); + } + @Test public void checkBackground_withAnyDrawable() throws Exception { - assertHasAnyBackground(R.id.view_with_backbround); + assertHasAnyBackground(R.id.view_with_drawable_backbround); } @Test(expected = BaristaException.class) @@ -311,7 +341,7 @@ public void checkBackground_withoutDrawable() throws Exception { @Test(expected = BaristaException.class) public void checkBackground_withoutDrawable_failure() throws Exception { - assertHasNoBackground(R.id.view_with_backbround); + assertHasNoBackground(R.id.view_with_drawable_backbround); } @Test @@ -386,4 +416,14 @@ public void checkTextViewDoesntContainsText_withoutViewId_failsWhenNeeded() { public void checkTextViewDoesntContainsResourceText_withoutViewId_failsWhenNeeded() { assertNotContains(R.string.enabled); } + + @Test(expected = BaristaException.class) + public void checkVectorDrawable_fails() { + assertHasDrawable(R.id.vector_image_view, R.drawable.barista_logo_vector_2); + } + + @Test(expected = BaristaException.class) + public void checkAnyDrawable_fails() { + assertHasAnyDrawable(R.id.no_drawable_image); + } } diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ChipsTest.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ChipsTest.kt new file mode 100644 index 000000000..c2c3668a9 --- /dev/null +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ChipsTest.kt @@ -0,0 +1,79 @@ +package com.schibsted.spain.barista.sample + +import androidx.test.rule.ActivityTestRule +import com.schibsted.spain.barista.assertion.BaristaCheckedAssertions.assertChecked +import com.schibsted.spain.barista.assertion.BaristaCheckedAssertions.assertUnchecked +import com.schibsted.spain.barista.assertion.BaristaChipAssertions.assertAnyChipSelected +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaChipInteractions.closeChip +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.internal.failurehandler.BaristaException +import com.schibsted.spain.barista.sample.util.SpyFailureHandlerRule +import junit.framework.AssertionFailedError +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.ThrowableAssert.catchThrowable +import org.junit.Rule +import org.junit.Test + +class ChipsTest { + + @get:Rule + var activityRule = ActivityTestRule(ChipsActivity::class.java) + + @get:Rule + var spyFailureHandlerRule = SpyFailureHandlerRule() + + @Test + fun assertCheckedChip() { + assertChecked(R.id.checkedChip) + + spyFailureHandlerRule.assertNoEspressoFailures() + } + + @Test + fun assertUncheckedChip() { + assertUnchecked(R.id.uncheckedChip) + + spyFailureHandlerRule.assertNoEspressoFailures() + } + + @Test + fun assertUncheckedChip_failsWhenNeeded() { + val thrown = catchThrowable{assertUnchecked(R.id.checkedChip)} + + spyFailureHandlerRule.assertEspressoFailures(1) + + assertThat(thrown).isInstanceOf(BaristaException::class.java) + .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/checkedChip) didn't match condition (not with checkbox state: is )") + .hasCauseInstanceOf(AssertionFailedError::class.java) + } + + @Test + fun checkOnChipOnSingleChoiceChipGroup_hasOnlyOneSelectedChip() { + clickOn(R.id.chip1) + + assertAnyChipSelected(R.id.singleSelectionChildGroup) + + spyFailureHandlerRule.assertNoEspressoFailures() + } + + @Test + fun checkOnChipOnSingleChoiceChipGroup_hasOnlyOneSelectedChip_failsWhenNeeded() { + val thrown = catchThrowable{assertAnyChipSelected(R.id.singleSelectionChildGroup)} + + spyFailureHandlerRule.assertEspressoFailures(1) + + assertThat(thrown).isInstanceOf(BaristaException::class.java) + .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/singleSelectionChildGroup) didn't match condition (custom condition [use `assertionDescription` parameter on `assertAny` to setup descriptive message])") + .hasCauseInstanceOf(AssertionFailedError::class.java) + } + + @Test + fun testCloseChip() { + closeChip(R.id.closeChip) + + assertDisplayed("A chip has been closed") + + spyFailureHandlerRule.assertNoEspressoFailures() + } +} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorResourceTypeTest.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorResourceTypeTest.kt new file mode 100644 index 000000000..15f3b49c8 --- /dev/null +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorResourceTypeTest.kt @@ -0,0 +1,34 @@ +package com.schibsted.spain.barista.sample + +import android.graphics.Color +import com.schibsted.spain.barista.internal.util.ColorResourceType +import com.schibsted.spain.barista.internal.util.colorResourceType +import junit.framework.Assert.assertEquals +import org.junit.Test + +class ColorResourceTypeTest { + + @Test + fun color_resource_type_color_res() { + val type = R.color.blue.colorResourceType + assertEquals(type, ColorResourceType.COLOR_RES) + } + + @Test + fun color_resource_type_color_attr() { + val type = R.attr.colorPrimary.colorResourceType + assertEquals(type, ColorResourceType.COLOR_ATTR) + } + + @Test + fun color_resource_type_color_int() { + val type = Color.BLUE.colorResourceType + assertEquals(type, ColorResourceType.COLOR_INT) + } + + @Test + fun color_resource_type_color_state_list() { + val type = R.color.selector_default_disabled_checked.colorResourceType + assertEquals(type, ColorResourceType.COLOR_RES) + } +} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsSecondaryTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsSecondaryTest.java new file mode 100644 index 000000000..bb2d87cb5 --- /dev/null +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsSecondaryTest.java @@ -0,0 +1,59 @@ +package com.schibsted.spain.barista.sample; + +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; +import com.schibsted.spain.barista.internal.failurehandler.BaristaException; +import com.schibsted.spain.barista.sample.util.FailureHandlerValidatorRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertTextColorIs; +import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertTextColorIsNot; + +@RunWith(AndroidJUnit4.class) +public class ColorsSecondaryTest { + + @Rule + public ActivityTestRule activityRule = new ActivityTestRule<>(ColorsSecondActivity.class); + + @Rule + public FailureHandlerValidatorRule handlerValidator = new FailureHandlerValidatorRule(); + + @Test + public void checkColorStyleable_colorStateList() { + assertTextColorIs( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_ColorState, + R.styleable.SampleCustomView_customColor + ); + + assertTextColorIsNot( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_ColorState_Secondary, + R.styleable.SampleCustomView_otherColor + ); + } + + @Test(expected = BaristaException.class) + public void checkColorStyleable_colorStateList_fail() { + assertTextColorIs( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_ColorState_Secondary, + R.styleable.SampleCustomView_customColor + ); + } + + @Test(expected = BaristaException.class) + public void checkNotColorStyleable_colorStateList_fail() { + assertTextColorIsNot( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_ColorState, + R.styleable.SampleCustomView_customColor + ); + } +} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsTest.java index 9c3d9e6e3..5451d7c3c 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ColorsTest.java @@ -1,5 +1,6 @@ package com.schibsted.spain.barista.sample; +import android.graphics.Color; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.schibsted.spain.barista.internal.failurehandler.BaristaException; @@ -9,6 +10,7 @@ import org.junit.runner.RunWith; import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertTextColorIs; +import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertTextColorIsNot; @RunWith(AndroidJUnit4.class) public class ColorsTest { @@ -30,6 +32,42 @@ public void checkColorList_whenDefault() { assertTextColorIs(R.id.textSelectorDefault, R.color.defaultColor); } + @Test + public void checkColorAttribute() { + assertTextColorIs(R.id.textColorAttribute, R.attr.colorPrimary); + assertTextColorIsNot(R.id.textColorAttribute, R.attr.colorError); + } + + @Test + public void checkColorInt() { + assertTextColorIs(R.id.textColorInt, Color.parseColor("#ff0000")); + assertTextColorIsNot(R.id.textColorInt, Color.parseColor("#ff00ff")); + } + + @Test + public void checkColorStyleable() { + assertTextColorIs( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle, + R.styleable.SampleCustomView_customColor + ); + + assertTextColorIsNot( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_Green, + R.styleable.SampleCustomView_customColor + ); + + assertTextColorIsNot( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle, + R.styleable.SampleCustomView_otherColor + ); + } + @Test public void checkColorList_whenDisabled() { assertTextColorIs(R.id.textSelectorDisabled, R.color.selector_default_disabled_checked); @@ -47,6 +85,31 @@ public void checkSimpleColor_fails() { assertTextColorIs(R.id.textRed, R.color.blue); } + @Test(expected = BaristaException.class) + public void checkColorAttr_fails() { + assertTextColorIs(R.id.textColorAttribute, R.attr.colorError); + } + + @Test(expected = BaristaException.class) + public void checkColorInt_fails() { + assertTextColorIs(R.id.textColorInt, Color.parseColor("#00ff00")); + } + + @Test(expected = BaristaException.class) + public void checkNotSimpleColor_fails() { + assertTextColorIsNot(R.id.textRed, R.color.red); + } + + @Test(expected = BaristaException.class) + public void checkNotColorAttr_fails() { + assertTextColorIsNot(R.id.textColorAttribute, R.attr.colorPrimary); + } + + @Test(expected = BaristaException.class) + public void checkNotColorInt_fails() { + assertTextColorIsNot(R.id.textColorInt, Color.parseColor("#ff0000")); + } + @Test(expected = BaristaException.class) public void checkColorList_whenDefault_fails() { assertTextColorIs(R.id.textSelectorDefault, R.color.checked); @@ -61,4 +124,24 @@ public void checkColorList_whenDisabled_fails() { public void checkColorList_whenChecked_fails() { assertTextColorIs(R.id.textSelectorChecked, R.color.disabled); } + + @Test(expected = BaristaException.class) + public void checkColorStyleable_fail() { + assertTextColorIs( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle_Green, + R.styleable.SampleCustomView_customColor + ); + } + + @Test(expected = BaristaException.class) + public void checkNotColorStyleable_fail() { + assertTextColorIsNot( + R.id.customTextView, + R.styleable.SampleCustomView, + R.style.SampleCustomStyle, + R.styleable.SampleCustomView_customColor + ); + } } \ No newline at end of file diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/DayNightTestRuleTest.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/DayNightTestRuleTest.kt new file mode 100644 index 000000000..143ad833b --- /dev/null +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/DayNightTestRuleTest.kt @@ -0,0 +1,34 @@ +package com.schibsted.spain.barista.sample + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.rule.ActivityTestRule +import com.schibsted.spain.barista.rule.theme.DayNightRule +import org.junit.Rule +import org.junit.Test + +class DayNightTestRuleTest { + + @get:Rule + val activityRule = ActivityTestRule(HelloWorldActivity::class.java, true, false) + + @get:Rule + val dayNightRule = DayNightRule() + + @Test + fun someDeterministicTest() { + activityRule.launchActivity(null) + onView(withId(R.id.some_view)).check(matches(isDisplayed())) + } + + @Test + fun testWillFailOnDayMode() { + try { + activityRule.launchActivity(null) + onView(withId(R.id.text_night)).check(matches(isDisplayed())) + } catch (t: Throwable) { + } + } +} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/FlakyTestRuleTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/FlakyTestRuleTest.java index d3b59f675..0f3484397 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/FlakyTestRuleTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/FlakyTestRuleTest.java @@ -4,20 +4,15 @@ import com.schibsted.spain.barista.rule.flaky.AllowFlaky; import com.schibsted.spain.barista.rule.flaky.FlakyTestRule; import com.schibsted.spain.barista.rule.flaky.Repeat; -import java.util.Random; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; - public class FlakyTestRuleTest { - private final Random random = new Random(); + private static boolean hasRunAllowFlakyTest = false; + private static boolean hasRunRepeatTest = false; private ActivityTestRule activityRule = new ActivityTestRule<>(HelloWorldActivity.class, true, false); private FlakyTestRule flakyRule = new FlakyTestRule() @@ -29,35 +24,32 @@ public class FlakyTestRuleTest { // WARNING: this test must fail when run @Test - @Repeat(times = 5) + @Repeat(times = 2) @Ignore - public void someImportantFlakyTest() throws Exception { + public void repeatAnnotationTest() throws Exception { activityRule.launchActivity(null); - onView(withId(R.id.some_view)).check(matches(isDisplayed())); - - if (random.nextFloat() > 0.3) { - throw new TestException("Random test failure"); + if (hasRunRepeatTest) { + throw new TestException("Test failure on second try"); + } else { + hasRunRepeatTest = true; } } @Test - @AllowFlaky(attempts = 10) - public void someFlakyTest() throws Exception { + @AllowFlaky(attempts = 2) + public void allowFlakyAnnotationTest() throws Exception { activityRule.launchActivity(null); - onView(withId(R.id.some_view)).check(matches(isDisplayed())); - - if (random.nextFloat() > 0.3) { - throw new TestException("Random test failure"); + if (!hasRunAllowFlakyTest) { + hasRunAllowFlakyTest = true; + throw new TestException("Test failure on first try"); } } @Test - public void someDeterministicTest() throws Exception { + public void testWithoutAnnotations() throws Exception { activityRule.launchActivity(null); - - onView(withId(R.id.some_view)).check(matches(isDisplayed())); } private static class TestException extends Exception { diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListViewAssertionTest.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListViewAssertionTest.kt index 0217f0626..ec68a8c07 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListViewAssertionTest.kt +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListViewAssertionTest.kt @@ -1,6 +1,6 @@ package com.schibsted.spain.barista.sample -import androidx.test.InstrumentationRegistry +import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.rule.ActivityTestRule @@ -148,7 +148,7 @@ class ListViewAssertionTest { } private fun openActivity(intentBuilder: ListsActivity.IntentBuilder) { - activityTestRule.launchActivity(intentBuilder.build(InstrumentationRegistry.getTargetContext())) + activityTestRule.launchActivity(intentBuilder.build(ApplicationProvider.getApplicationContext())) } private fun openSimpleListActivity() { diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsChildTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsChildTest.java index 421da2028..fd64a2e91 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsChildTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsChildTest.java @@ -1,6 +1,6 @@ package com.schibsted.spain.barista.sample; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; import androidx.test.espresso.PerformException; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; @@ -175,7 +175,7 @@ public void fails_whenListViewChildNotExist() { } private void openActivity(IntentBuilder intentBuilder) { - activity.launchActivity(intentBuilder.build(InstrumentationRegistry.getTargetContext())); + activity.launchActivity(intentBuilder.build(ApplicationProvider.getApplicationContext())); closeKeyboard(); } diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsClickTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsClickTest.java index 0de236702..db83088d4 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsClickTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsClickTest.java @@ -1,6 +1,6 @@ package com.schibsted.spain.barista.sample; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.schibsted.spain.barista.internal.failurehandler.BaristaException; @@ -228,6 +228,6 @@ private void assertResult(String text) { } private void launchTestActivity(ListsActivity.IntentBuilder intentBuilder) { - activity.launchActivity(intentBuilder.build(InstrumentationRegistry.getTargetContext())); + activity.launchActivity(intentBuilder.build(ApplicationProvider.getApplicationContext())); } } diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsScrollTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsScrollTest.java index ade912e80..f02de8955 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsScrollTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ListsScrollTest.java @@ -1,6 +1,6 @@ package com.schibsted.spain.barista.sample; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.schibsted.spain.barista.sample.util.FailureHandlerValidatorRule; @@ -72,6 +72,6 @@ public void scrollMultipleRecyclerView_byId() throws Exception { } private void openActivity(IntentBuilder intentBuilder) { - activity.launchActivity(intentBuilder.build(InstrumentationRegistry.getTargetContext())); + activity.launchActivity(intentBuilder.build(ApplicationProvider.getApplicationContext())); } } diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/MenuClickTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/MenuClickTest.java index 03eee1f25..d13c0652c 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/MenuClickTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/MenuClickTest.java @@ -1,5 +1,6 @@ package com.schibsted.spain.barista.sample; +import androidx.test.filters.FlakyTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.schibsted.spain.barista.internal.failurehandler.BaristaException; @@ -10,6 +11,7 @@ import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed; import static com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.clickMenu; +import static com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.openMenu; @RunWith(AndroidJUnit4.class) public class MenuClickTest { @@ -45,17 +47,27 @@ public void iconMenuClick_byTitle() { } @Test + @FlakyTest(detail = "This tests passes on local, but fails on CI") public void overflowMenuClick_byId() { clickMenu(R.id.menu_action_3); assertDisplayed("Third menu option"); } @Test + @FlakyTest(detail = "This tests passes on local, but fails on CI") public void overflowMenuClick_byTitle() { clickMenu("Menu 3"); assertDisplayed("Third menu option"); } + @Test + @FlakyTest(detail = "This tests passes on local, but fails on CI") + public void openOverflowMenu_withoutClickingAnyOption() { + openMenu(); + assertDisplayed("Menu 3"); + assertDisplayed("Menu 4"); + } + @Test(expected = BaristaException.class) public void missingMenuClickFails_byId() throws Exception { clickMenu(R.id.button); diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/PermissionGranterTest.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/PermissionGranterTest.kt index 6c8f983a3..0b9b530c5 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/PermissionGranterTest.kt +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/PermissionGranterTest.kt @@ -1,60 +1,93 @@ package com.schibsted.spain.barista.sample import android.Manifest -import android.content.Context import android.content.pm.PackageManager import android.os.Build -import androidx.annotation.RequiresApi -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.test.core.app.ActivityScenario import com.schibsted.spain.barista.interaction.PermissionGranter -import com.schibsted.spain.barista.internal.failurehandler.BaristaException -import com.schibsted.spain.barista.rule.BaristaRule -import org.junit.Assume.assumeFalse +import org.junit.Assert.assertEquals import org.junit.Assume.assumeTrue import org.junit.Before -import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException class PermissionGranterTest { - @get:Rule - var thrown = ExpectedException.none() - - @get:Rule - var activityRule = BaristaRule.create(RuntimePermissionActivity::class.java) - @Before - @Throws(Exception::class) fun setUp() { - assumeTrue("This test needs to run on a device with Android 23 or above", - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - assumeFalse("This test expects you to not have the permission granted. Remember to clear data.", - hasNeededPermission(getApplicationContext(), RuntimePermissionActivity.SOME_PERMISSION)) + assumeTrue("This test needs to run on a device with Android 23 or above", Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + } + + @Test + fun grants_simple_permission() { + launchActivity { + verifyPermissionNotGranted(PERMISSION_1_CONTACTS) + requestPermission(PERMISSION_1_CONTACTS) + + PermissionGranter.allowPermissionsIfNeeded(PERMISSION_1_CONTACTS) + + verifyPermissionGranted(PERMISSION_1_CONTACTS) + } } - @Test(expected = BaristaException::class) - fun fails_when_using_permission() { - activityRule.launchActivity() + /** + * Since Android Q the Location permission dialog is different to the others + */ + @Test + fun grants_location_permission() { + launchActivity { + verifyPermissionNotGranted(LOCATION_PERMISSION) + requestPermission(LOCATION_PERMISSION) - clickOn(R.id.use_permission_button) + PermissionGranter.allowPermissionsIfNeeded(LOCATION_PERMISSION) + + verifyPermissionGranted(LOCATION_PERMISSION) + } } @Test - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) - fun works_after_granting_permission() { - activityRule.launchActivity() + fun ignores_already_granted_permission() { + launchActivity { + verifyPermissionNotGranted(PERMISSION_2_CAMERA) + requestPermission(PERMISSION_2_CAMERA) + PermissionGranter.allowPermissionOneTime(PERMISSION_2_CAMERA) + verifyPermissionGranted(PERMISSION_2_CAMERA) + + PermissionGranter.allowPermissionsIfNeeded(PERMISSION_2_CAMERA) + } + } + + +} + +// We can't reuse permission from one test to another, because they stay granted after each test +private const val PERMISSION_1_CONTACTS = Manifest.permission.READ_CONTACTS +private const val PERMISSION_2_CAMERA = Manifest.permission.CAMERA +private const val LOCATION_PERMISSION = Manifest.permission.ACCESS_FINE_LOCATION - clickOn(R.id.request_permission_button) +private fun ActivityScenario<*>.requestPermission(permission: String) { + onActivity { activity -> + ActivityCompat.requestPermissions(activity, arrayOf(permission), 1) + } +} - PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.READ_CONTACTS) +private fun ActivityScenario<*>.verifyPermissionNotGranted(permission: String) { + onActivity { activity -> + val permissionValue = ContextCompat.checkSelfPermission(activity, permission) + assertEquals("Permission $permission expected to be denied but was granted;", PackageManager.PERMISSION_DENIED, permissionValue) + } +} - clickOn(R.id.use_permission_button) +private fun ActivityScenario<*>.verifyPermissionGranted(permission: String) { + onActivity { activity -> + val permissionValue = ContextCompat.checkSelfPermission(activity, permission) + assertEquals("Expected permission $permission was not granted;", PackageManager.PERMISSION_GRANTED, permissionValue) } +} - private fun hasNeededPermission(context: Context, permissionNeeded: String): Boolean { - val permissionStatus = context.checkPermission(permissionNeeded, android.os.Process.myPid(), android.os.Process.myUid()) - return permissionStatus == PackageManager.PERMISSION_GRANTED +private fun launchActivity(block: ActivityScenario<*>.() -> Unit) { + ActivityScenario.launch(EmptyActivity::class.java).use { scenario -> + scenario.block() } } diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ScrollsTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ScrollsTest.java index 125db8282..3727de406 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ScrollsTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ScrollsTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static androidx.test.espresso.matcher.ViewMatchers.withId; import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed; import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertNotDisplayed; import static com.schibsted.spain.barista.interaction.BaristaScrollInteractions.safelyScrollTo; @@ -22,6 +23,15 @@ public class ScrollsTest { @Rule public FailureHandlerValidatorRule handlerValidator = new FailureHandlerValidatorRule(); + @Test + public void scrollsInsideScrollable_byCustomMatcher() { + assertTopVisible(); + + scrollTo(withId(R.id.really_far_away_button)); + + assertBottomVisible(); + } + @Test public void scrollsInsideScrollable_byId() { assertTopVisible(); @@ -56,6 +66,15 @@ public void scrollsOutsideScrollableFails() throws Exception { scrollTo(R.id.centered_button); } + @Test + public void safelyScrollsInsideScrollable_byCustomMatcher() throws Exception { + assertTopVisible(); + + safelyScrollTo(withId(R.id.really_far_away_button)); + + assertBottomVisible(); + } + @Test public void safelyScrollsInsideScrollable_byId() throws Exception { assertTopVisible(); diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ViewPager2Test.kt b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ViewPager2Test.kt new file mode 100644 index 000000000..e314932b7 --- /dev/null +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/ViewPager2Test.kt @@ -0,0 +1,149 @@ +package com.schibsted.spain.barista.sample + +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaViewPagerInteractions.swipeViewPagerBack +import com.schibsted.spain.barista.interaction.BaristaViewPagerInteractions.swipeViewPagerForward +import com.schibsted.spain.barista.sample.ViewPager2Activity.Orientation.HORIZONTAL +import com.schibsted.spain.barista.sample.ViewPager2Activity.Orientation.VERTICAL +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This test class will cover swiping forward/backward, up/down, and check that it doesn't crash + * when trying to swipe when on the last page for all four directions. It will be repeated for the + * API's with and without passing a view id. + */ +@RunWith(AndroidJUnit4ClassRunner::class) +class ViewPager2Test { + + @get:Rule + var activityRule = ActivityTestRule(ViewPager2Activity::class.java, true) + + @Test + fun checkHorizontalSwipeForward() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward() + assertDisplayed("2") + } + + @Test + fun checkHorizontalSwipeForward_withId() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward(R.id.viewPager2) + assertDisplayed("2") + } + + @Test + fun checkHorizontalSwipeBack() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward() + swipeViewPagerBack() + assertDisplayed("1") + } + + @Test + fun checkHorizontalSwipeBack_withId() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward(R.id.viewPager2) + swipeViewPagerBack(R.id.viewPager2) + assertDisplayed("1") + } + + @Test + fun checkVerticalSwipeForward() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward() + assertDisplayed("2") + } + + @Test + fun checkVerticalSwipeForward_withId() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward(R.id.viewPager2) + assertDisplayed("2") + } + + @Test + fun checkVerticalSwipeBack() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward() + swipeViewPagerBack() + assertDisplayed("1") + } + + @Test + fun checkVerticalSwipeBack_withId() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward(R.id.viewPager2) + swipeViewPagerBack(R.id.viewPager2) + assertDisplayed("1") + } + + @Test + fun swipingForwardHorizontallyWhileAtTheLastPageDoesNotCrash() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward() + swipeViewPagerForward() + assertDisplayed("2") + } + + @Test + fun swipingForwardHorizontallyWhileAtTheLastPageDoesNotCrash_withId() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerForward(R.id.viewPager2) + swipeViewPagerForward(R.id.viewPager2) + assertDisplayed("2") + } + + @Test + fun swipingBackHorizontallyWhileAtTheFirstPageDoesNotCrash() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerBack() + assertDisplayed("1") + } + + @Test + fun swipingBackHorizontallyWhileAtTheFirstPageDoesNotCrash_withId() { + setViewPager(orientation = HORIZONTAL) + swipeViewPagerBack(R.id.viewPager2) + assertDisplayed("1") + } + + @Test + fun swipingForwardVerticallyWhileAtTheLastPageDoesNotCrash() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward() + swipeViewPagerForward() + assertDisplayed("2") + } + + @Test + fun swipingForwardVerticallyWhileAtTheLastPageDoesNotCrash_withId() { + setViewPager(orientation = VERTICAL) + swipeViewPagerForward(R.id.viewPager2) + swipeViewPagerForward(R.id.viewPager2) + assertDisplayed("2") + } + + @Test + fun swipingBackVerticallyWhileAtTheFirstPageDoesNotCrash() { + setViewPager(orientation = VERTICAL) + swipeViewPagerBack() + assertDisplayed("1") + } + + @Test + fun swipingBackVerticallyWhileAtTheFirstPageDoesNotCrash_withId() { + setViewPager(orientation = VERTICAL) + swipeViewPagerBack(R.id.viewPager2) + assertDisplayed("1") + } + + /** Helper function */ + private fun setViewPager(orientation: ViewPager2Activity.Orientation) { + activityRule.activity.set(orientation = orientation) + } +} diff --git a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/assertion/VisibilityAssertionsTest.java b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/assertion/VisibilityAssertionsTest.java index 55e9f2124..221b60096 100644 --- a/sample/src/androidTest/java/com/schibsted/spain/barista/sample/assertion/VisibilityAssertionsTest.java +++ b/sample/src/androidTest/java/com/schibsted/spain/barista/sample/assertion/VisibilityAssertionsTest.java @@ -53,22 +53,39 @@ public void checkNotDisplayed_withGoneView() { @Test public void checkDisplayedViews_failsWhenInvisible() { - Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.invisible_view)); - - spyFailureHandlerRule.assertEspressoFailures(1); - assertThat(thrown).isInstanceOf(BaristaException.class) + Throwable thrown1 = catchThrowable(() -> assertDisplayed(R.id.invisible_view)); + assertThat(thrown1).isInstanceOf(BaristaException.class) .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/invisible_view) " + "didn't match condition (is displayed on the screen to the user)"); + + Throwable thrown2 = catchThrowable(() -> assertDisplayed(R.string.im_invisible)); // test fails + assertThat(thrown2).isInstanceOf(BaristaException.class) + .hasMessageMatching("View \\(with string from resource id: <(\\d+)>\\[im_invisible\\] value: I'm invisible!\\) " + + "didn't match condition \\(is displayed on the screen to the user\\)"); + + Throwable thrown3 = catchThrowable(() -> assertDisplayed("I'm invisible!")); + assertThat(thrown3).isInstanceOf(BaristaException.class) + .hasMessage("View (with text: is \"I'm invisible!\") " + + "didn't match condition (is displayed on the screen to the user)"); + + spyFailureHandlerRule.assertEspressoFailures(3); } @Test public void checkDisplayed_failsWhenNotExists() { - Throwable thrown = catchThrowable(() -> assertDisplayed(R.string.not_exists)); - - spyFailureHandlerRule.assertEspressoFailures(1); + Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.no)); assertThat(thrown).isInstanceOf(BaristaException.class) - .hasMessageContaining("No view matching (with string from resource id") - .hasMessageContaining("[not_exists] value: Not exists) was found"); + .hasMessageContaining("No view matching (with id: com.schibsted.spain.barista.sample:id/no) was found"); + + Throwable thrown2 = catchThrowable(() -> assertDisplayed(R.string.not_exists)); + assertThat(thrown2).isInstanceOf(BaristaException.class) + .hasMessageMatching("No view matching \\(with string from resource id: <(\\d+)>\\[not_exists\\] value: Not exists\\) was found"); + + Throwable thrown3 = catchThrowable(() -> assertDisplayed("Not exists")); + assertThat(thrown3).isInstanceOf(BaristaException.class) + .hasMessageContaining("No view matching (with text: is \"Not exists\") was found"); + + spyFailureHandlerRule.assertEspressoFailures(3); } @Test @@ -81,21 +98,46 @@ public void checkDisplayedIdAndText() { @Test public void checkDisplayedIdAndText_failsWhenTextIsNotTheExpected() { - Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.visible_view, "This is not the text you are looking for")); - - spyFailureHandlerRule.assertEspressoFailures(1); + Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.visible_view, R.string.not_exists)); assertThat(thrown).isInstanceOf(BaristaException.class) + .hasMessageMatching("View \\(with id: com.schibsted.spain.barista.sample:id/visible_view\\) didn't match condition " + + "\\(\\(is displayed on the screen to the user " + + "and with string from resource id: <(\\d+)>\\[not_exists\\] value: Not exists\\)\\)"); + + Throwable thrown2 = catchThrowable(() -> assertDisplayed(R.id.visible_view, "This is not the text you are looking for")); + assertThat(thrown2).isInstanceOf(BaristaException.class) .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/visible_view) didn't match condition " - + "(with text: is \"This is not the text you are looking for\")"); + + "((is displayed on the screen to the user and with text: is \"This is not the text you are looking for\"))"); + + spyFailureHandlerRule.assertEspressoFailures(2); } @Test - public void checkDisplayedIdAndText_failsWhenViewDoesNotExist() { - Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.not_exists, "This is not the text you are looking for")); + public void checkDisplayedIdAndText_failsWhenInvisible() { + Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.invisible_view, R.string.im_invisible)); + assertThat(thrown).isInstanceOf(BaristaException.class) + .hasMessageMatching("View \\(with id: com.schibsted.spain.barista.sample:id/invisible_view\\) didn't match condition " + + "\\(\\(is displayed on the screen to the user and with string from resource id: <(\\d+)>\\)\\)"); - spyFailureHandlerRule.assertEspressoFailures(1); + Throwable thrown2 = catchThrowable(() -> assertDisplayed(R.id.invisible_view, "I'm invisible!")); + assertThat(thrown2).isInstanceOf(BaristaException.class) + .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/invisible_view) didn't match condition " + + "((is displayed on the screen to the user and with text: is \"I'm invisible!\"))"); + + spyFailureHandlerRule.assertEspressoFailures(2); + } + + @Test + public void checkDisplayedIdAndText_failsWhenViewDoesNotExist() { + Throwable thrown = catchThrowable(() -> assertDisplayed(R.id.not_exists, R.string.im_invisible)); assertThat(thrown).isInstanceOf(BaristaException.class) .hasMessage("No view matching (with id: com.schibsted.spain.barista.sample:id/not_exists) was found"); + + Throwable thrown2 = catchThrowable(() -> assertDisplayed(R.id.not_exists, "This is not the text you are looking for")); + assertThat(thrown2).isInstanceOf(BaristaException.class) + .hasMessage("No view matching (with id: com.schibsted.spain.barista.sample:id/not_exists) was found"); + + spyFailureHandlerRule.assertEspressoFailures(2); } @Test @@ -123,7 +165,7 @@ public void checkNotDisplayedIdAndText_failsWhenTextMatches() { spyFailureHandlerRule.assertEspressoFailures(1); assertThat(thrown).isInstanceOf(BaristaException.class) .hasMessage("View (with id: com.schibsted.spain.barista.sample:id/visible_view) " - + "didn't match condition (not with text: is \"Hello world!\")"); + + "didn't match condition (not (is displayed on the screen to the user and with text: is \"Hello world!\"))"); } @Test diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index ad1df3fe4..82ad66428 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ > + + - - + @@ -27,15 +28,16 @@ + - - + + - + + diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/CameraActivity.kt b/sample/src/main/java/com/schibsted/spain/barista/sample/CameraActivity.kt index c95cdacf3..388a00d6f 100644 --- a/sample/src/main/java/com/schibsted/spain/barista/sample/CameraActivity.kt +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/CameraActivity.kt @@ -1,5 +1,6 @@ package com.schibsted.spain.barista.sample +import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.os.Bundle @@ -29,6 +30,7 @@ class CameraActivity : AppCompatActivity() { } } + @SuppressLint("MissingSuperCall") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == TAKE_PICTURE_REQUEST_CODE) { Glide.with(this).load(getPictureUri()).into(image_view) diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/ChipsActivity.kt b/sample/src/main/java/com/schibsted/spain/barista/sample/ChipsActivity.kt new file mode 100644 index 000000000..fac0864cb --- /dev/null +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/ChipsActivity.kt @@ -0,0 +1,20 @@ +package com.schibsted.spain.barista.sample + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_chips.closeChip +import kotlinx.android.synthetic.main.activity_chips.closeChipText + +class ChipsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_chips) + + closeChip.setOnCloseIconClickListener { + closeChipText.visibility = View.VISIBLE + } + } + +} diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/ColorsSecondActivity.java b/sample/src/main/java/com/schibsted/spain/barista/sample/ColorsSecondActivity.java new file mode 100644 index 000000000..7ede2cd08 --- /dev/null +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/ColorsSecondActivity.java @@ -0,0 +1,14 @@ +package com.schibsted.spain.barista.sample; + +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +public class ColorsSecondActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_color_second); + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/EmptyActivity.kt b/sample/src/main/java/com/schibsted/spain/barista/sample/EmptyActivity.kt new file mode 100644 index 000000000..686bca7a2 --- /dev/null +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/EmptyActivity.kt @@ -0,0 +1,5 @@ +package com.schibsted.spain.barista.sample + +import androidx.appcompat.app.AppCompatActivity + +class EmptyActivity : AppCompatActivity() \ No newline at end of file diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/HelloWorldActivity.java b/sample/src/main/java/com/schibsted/spain/barista/sample/HelloWorldActivity.java index f7a3ccb63..d097913e5 100644 --- a/sample/src/main/java/com/schibsted/spain/barista/sample/HelloWorldActivity.java +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/HelloWorldActivity.java @@ -1,9 +1,11 @@ package com.schibsted.spain.barista.sample; import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.Button; +import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; public class HelloWorldActivity extends AppCompatActivity { @@ -20,5 +22,13 @@ public void onClick(View v) { finish(); } }); + + TextView textNight = findViewById(R.id.text_night); + + if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) { + textNight.setVisibility(View.VISIBLE); + } else { + textNight.setVisibility(View.GONE); + } } } diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/RuntimePermissionActivity.java b/sample/src/main/java/com/schibsted/spain/barista/sample/RuntimePermissionActivity.java deleted file mode 100644 index 692e966f3..000000000 --- a/sample/src/main/java/com/schibsted/spain/barista/sample/RuntimePermissionActivity.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.schibsted.spain.barista.sample; - -import android.Manifest; -import android.annotation.TargetApi; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.appcompat.app.AppCompatActivity; -import android.view.View; - -@TargetApi(Build.VERSION_CODES.M) -public class RuntimePermissionActivity extends AppCompatActivity { - - public static final String SOME_PERMISSION = Manifest.permission.READ_CONTACTS; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_runtime_permission); - - findViewById(R.id.request_permission_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - requestPermission(); - } - }); - - findViewById(R.id.use_permission_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - usePermission(); - } - }); - } - - private void requestPermission() { - ActivityCompat.requestPermissions(this, new String[] { SOME_PERMISSION }, 1); - } - - private void usePermission() { - if (ContextCompat.checkSelfPermission(this, SOME_PERMISSION) != PackageManager.PERMISSION_GRANTED) { - throw new IllegalStateException("Tried to use permission without having it"); - } - } -} diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/SomeViewsWithDifferentVisibilitiesActivity.java b/sample/src/main/java/com/schibsted/spain/barista/sample/SomeViewsWithDifferentVisibilitiesActivity.java index 6d6129a2f..5341bcdac 100644 --- a/sample/src/main/java/com/schibsted/spain/barista/sample/SomeViewsWithDifferentVisibilitiesActivity.java +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/SomeViewsWithDifferentVisibilitiesActivity.java @@ -1,6 +1,7 @@ package com.schibsted.spain.barista.sample; import android.os.Bundle; +import android.widget.ImageView; import androidx.appcompat.app.AppCompatActivity; public class SomeViewsWithDifferentVisibilitiesActivity extends AppCompatActivity { @@ -11,5 +12,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); findViewById(R.id.edittext_with_focus).requestFocus(); + + ImageView codeVectorImageView = findViewById(R.id.code_vector_image_view); + codeVectorImageView.setImageResource(R.drawable.barista_logo_vector); } } diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/ViewPager2Activity.kt b/sample/src/main/java/com/schibsted/spain/barista/sample/ViewPager2Activity.kt new file mode 100644 index 000000000..031a47329 --- /dev/null +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/ViewPager2Activity.kt @@ -0,0 +1,41 @@ +package com.schibsted.spain.barista.sample + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.schibsted.spain.barista.sample.ViewPager2Activity.Orientation.HORIZONTAL +import com.schibsted.spain.barista.sample.ViewPager2Activity.Orientation.VERTICAL +import kotlinx.android.synthetic.main.activity_viewpager2.viewPager2 + +class ViewPager2Activity : FragmentActivity() { + + enum class Orientation { + VERTICAL, + HORIZONTAL + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_viewpager2) + + viewPager2.adapter = ViewPager2Adapter(activity = this) + } + + private class ViewPager2Adapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { + override fun getItemCount(): Int = 2 + + override fun createFragment(position: Int): Fragment = when (position) { + 0 -> ViewPagerFirstFragment() + else -> ViewPagerSecondFragment() + } + } + + fun set(orientation: Orientation) = runOnUiThread { + viewPager2.orientation = when (orientation) { + VERTICAL -> ViewPager2.ORIENTATION_VERTICAL + HORIZONTAL -> ViewPager2.ORIENTATION_HORIZONTAL + } + } +} diff --git a/sample/src/main/java/com/schibsted/spain/barista/sample/widget/SampleCustomView.kt b/sample/src/main/java/com/schibsted/spain/barista/sample/widget/SampleCustomView.kt new file mode 100644 index 000000000..802f3f844 --- /dev/null +++ b/sample/src/main/java/com/schibsted/spain/barista/sample/widget/SampleCustomView.kt @@ -0,0 +1,38 @@ +package com.schibsted.spain.barista.sample.widget; + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.core.content.withStyledAttributes +import com.schibsted.spain.barista.sample.R +import kotlinx.android.synthetic.main.sample_custom_view.view.customTextView + +class SampleCustomView @JvmOverloads +constructor( + context: Context, + attributeSet: AttributeSet? = null, + defStyleAttr: Int = R.attr.sampleCustomViewStyle, + defStyleRes: Int = R.style.SampleCustomStyle +) : LinearLayout(context, attributeSet, defStyleAttr) { + + private var customColor: Int = Color.GRAY + + init { + context.withStyledAttributes( + attributeSet, + R.styleable.SampleCustomView, + defStyleAttr, + defStyleRes + ) { + customColor = getColor(R.styleable.SampleCustomView_customColor, customColor) + } + + LayoutInflater.from(context).inflate(R.layout.sample_custom_view, this, true) + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + + customTextView.text = "Demo text" + customTextView.setTextColor(customColor) + } +} \ No newline at end of file diff --git a/sample/src/main/res/color/secondary_selector_default_disabled_checked.xml b/sample/src/main/res/color/secondary_selector_default_disabled_checked.xml new file mode 100644 index 000000000..d6c737d85 --- /dev/null +++ b/sample/src/main/res/color/secondary_selector_default_disabled_checked.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/barista_logo_vector.xml b/sample/src/main/res/drawable/barista_logo_vector.xml new file mode 100644 index 000000000..6c2946f10 --- /dev/null +++ b/sample/src/main/res/drawable/barista_logo_vector.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/barista_logo_vector_2.xml b/sample/src/main/res/drawable/barista_logo_vector_2.xml new file mode 100644 index 000000000..0993f183e --- /dev/null +++ b/sample/src/main/res/drawable/barista_logo_vector_2.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/activity_chips.xml b/sample/src/main/res/layout/activity_chips.xml new file mode 100644 index 000000000..549718c4f --- /dev/null +++ b/sample/src/main/res/layout/activity_chips.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_color.xml b/sample/src/main/res/layout/activity_color.xml index bdd43f4c4..891bd664a 100644 --- a/sample/src/main/res/layout/activity_color.xml +++ b/sample/src/main/res/layout/activity_color.xml @@ -33,6 +33,26 @@ android:textColor="@color/selector_default_disabled_checked" /> + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_color_second.xml b/sample/src/main/res/layout/activity_color_second.xml new file mode 100644 index 000000000..e1039e7e5 --- /dev/null +++ b/sample/src/main/res/layout/activity_color_second.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_hello_world.xml b/sample/src/main/res/layout/activity_hello_world.xml index 1c0bb661e..9ac277a00 100644 --- a/sample/src/main/res/layout/activity_hello_world.xml +++ b/sample/src/main/res/layout/activity_hello_world.xml @@ -12,9 +12,16 @@ android:text="@string/hello_world" />