1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Relations | Fix | Filter create option for tags that already exist (#2237)

* Relations | Fix | Filter create option for tags that already exist

* Tech | Fix | Change compile android tests task

* Tech | Fix | add build folders ignore to global .gitignore

* Tech | Fix | Extract MockDataFactory.kt from test-utils

* Tech | Fix | Move return to the same line as checkNotNull

* Tech | Fix | Delete unused analytics property

* Relations | Fix | Add AddObjectRelationValueViewModelTest.kt test query with tags behaviour

* Tech | Fix | Rename :test:utils to :test:android-utils

* Tech | Enhancement | implement fake providers for AddObjectRelationViewModerl
This commit is contained in:
Sergey Boishtyan 2022-05-09 17:54:15 +03:00 committed by GitHub
parent 8e678c95d4
commit 58da3badfe
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 770 additions and 469 deletions

View file

@ -0,0 +1,30 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion compile_sdk
defaultConfig {
minSdkVersion min_sdk
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
}
dependencies {
implementation mainApplication.appcompat
implementation mainApplication.kotlin
implementation mainApplication.coroutinesAndroid
implementation mainApplication.androidxCore
implementation acceptanceTesting.espressoCore
implementation mainApplication.design
implementation mainApplication.recyclerView
}

View file

@ -0,0 +1 @@
<manifest package="com.anytypeio.anytype.test_utils" />

View file

@ -0,0 +1,17 @@
package com.anytypeio.anytype.test_utils
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class TestFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.blank, container, false)
}
}

View file

@ -0,0 +1,108 @@
package com.anytypeio.anytype.test_utils.utils
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.anytypeio.anytype.test_utils.utils.TestUtils.withRecyclerView
import com.anytypeio.anytype.test_utils.utils.espresso.HasChildViewWithText
import com.anytypeio.anytype.test_utils.utils.espresso.HasViewGroupChildViewWithText
import com.anytypeio.anytype.test_utils.utils.espresso.WithBackgroundColor
import com.anytypeio.anytype.test_utils.utils.espresso.WithChildViewCount
import com.anytypeio.anytype.test_utils.utils.espresso.WithTextColor
import com.anytypeio.anytype.test_utils.utils.espresso.WithoutBackgroundColor
import org.hamcrest.Matchers.not
fun Int.findItemAt(position: Int, layoutId: Int): ViewInteraction {
return onView(withRecyclerView(this).atPositionOnView(position, layoutId))
}
fun ViewInteraction.performClick(): ViewInteraction = perform(ViewActions.click())
fun Int.matchView(): ViewInteraction = onView(withId(this))
fun Int.performClick(): ViewInteraction = matchView().performClick()
fun Int.type(text: String) = matchView().perform(click(), typeText(text))
fun ViewInteraction.checkHasText(text: String) {
check(matches(ViewMatchers.withText(text)))
}
fun ViewInteraction.checkIsSelected() {
check(matches(ViewMatchers.isSelected()))
}
fun ViewInteraction.checkIsFocused() {
check(matches(ViewMatchers.isFocused()))
}
fun ViewInteraction.checkIsNotFocused() {
check(matches(not(ViewMatchers.isFocused())))
}
fun ViewInteraction.checkIsDisplayed() {
check(matches(isDisplayed()))
}
fun ViewInteraction.checkIsNotDisplayed() {
check(matches(not(isDisplayed())))
}
fun ViewInteraction.checkIsNotSelected() {
check(matches(not(ViewMatchers.isSelected())))
}
fun ViewInteraction.checkHasText(resId: Int) {
check(matches(ViewMatchers.withText(resId)))
}
fun ViewInteraction.checkHasTextColor(color: Int) {
check(matches(WithTextColor(color)))
}
fun ViewInteraction.checkHasBackgroundColor(color: Int) {
check(matches(WithBackgroundColor(color)))
}
fun ViewInteraction.checkHasNoBackground() {
check(matches(WithoutBackgroundColor()))
}
fun ViewInteraction.checkHasChildViewCount(count: Int) : ViewInteraction {
return check(matches(WithChildViewCount(count)))
}
fun Int.rVMatcher(): RecyclerViewMatcher =
RecyclerViewMatcher(this)
fun Int.checkRecyclerItemCount(expected: Int) = matchView().check(RecyclerViewItemCountAssertion(expected))
fun RecyclerViewMatcher.onItemView(pos: Int, target: Int): ViewInteraction {
return onView(atPositionOnView(pos, target))
}
fun ViewInteraction.checkHasChildViewWithText(
pos: Int,
text: String,
target: Int
) : ViewInteraction {
return check(matches(HasChildViewWithText(pos, text, target)))
}
fun ViewInteraction.checkHasViewGroupChildWithText(
pos: Int,
text: String
) : ViewInteraction {
return check(matches(HasViewGroupChildViewWithText(pos, text)))
}
fun RecyclerViewMatcher.onItem(pos: Int): ViewInteraction {
return onView(atPosition(pos))
}
fun RecyclerViewMatcher.checkIsRecyclerSize(expected: Int) {
recyclerViewId.matchView().check(RecyclerViewItemCountAssertion(expected))
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.test_utils.utils
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.matcher.ViewMatchers
import org.hamcrest.CoreMatchers
class RecyclerViewItemCountAssertion(val expected: Int) : ViewAssertion {
override fun check(view: View?, noViewFoundException: NoMatchingViewException?) {
if (noViewFoundException != null) throw noViewFoundException
val recycler = view as RecyclerView
val adapter = recycler.adapter
checkNotNull(adapter) { "Adapter wasn't set" }
ViewMatchers.assertThat(adapter.itemCount, CoreMatchers.`is`(expected))
}
}

View file

@ -0,0 +1,79 @@
package com.anytypeio.anytype.test_utils.utils;
import android.content.res.Resources;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class RecyclerViewMatcher {
final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idRecyclerDescription = Integer.toString(recyclerViewId);
String idTargetViewDescription = Integer.toString(targetViewId);
if (this.resources != null) {
try {
idRecyclerDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException e) {
idRecyclerDescription = String.format("%s (resource name not found)", recyclerViewId);
}
try {
idTargetViewDescription = this.resources.getResourceName(targetViewId);
} catch (Resources.NotFoundException e) {
idTargetViewDescription = String.format("%s (resource name not found)", targetViewId);
}
}
description.appendText("\nwith id: [" + idTargetViewDescription + "] inside: [" + idRecyclerDescription + "]");
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(position);
if (holder == null) {
throw new IllegalStateException(
"No view holder found at position: " + position +
". Actual child count: " + recyclerView.getChildCount()
);
}
childView = holder.itemView;
} else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}

View file

@ -0,0 +1,107 @@
package com.anytypeio.anytype.test_utils.utils;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.espresso.util.HumanReadables;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
public class TestUtils {
public static <VH extends RecyclerView.ViewHolder> ViewAction actionOnItemViewAtPosition(int position,
@IdRes
int viewId,
ViewAction viewAction) {
return new ActionOnItemViewAtPositionViewAction(position, viewId, viewAction);
}
private static final class ActionOnItemViewAtPositionViewAction<VH extends RecyclerView
.ViewHolder>
implements
ViewAction {
private final int position;
private final ViewAction viewAction;
private final int viewId;
private ActionOnItemViewAtPositionViewAction(int position,
@IdRes int viewId,
ViewAction viewAction) {
this.position = position;
this.viewAction = viewAction;
this.viewId = viewId;
}
public Matcher<View> getConstraints() {
return Matchers.allOf(new Matcher[]{
ViewMatchers.isAssignableFrom(RecyclerView.class), ViewMatchers.isDisplayed()
});
}
public String getDescription() {
return "actionOnItemAtPosition performing ViewAction: "
+ this.viewAction.getDescription()
+ " on item at position: "
+ this.position;
}
public void perform(UiController uiController, View view) {
RecyclerView recyclerView = (RecyclerView) view;
(new ScrollToPositionViewAction(this.position)).perform(uiController, view);
uiController.loopMainThreadUntilIdle();
View targetView = recyclerView.getChildAt(this.position).findViewById(this.viewId);
if (targetView == null) {
throw (new PerformException.Builder()).withActionDescription(this.toString())
.withViewDescription(
HumanReadables.describe(view))
.withCause(new IllegalStateException(
"No toView with id "
+ this.viewId
+ " found at position: "
+ this.position))
.build();
} else {
this.viewAction.perform(uiController, targetView);
}
}
}
private static final class ScrollToPositionViewAction implements ViewAction {
private final int position;
private ScrollToPositionViewAction(int position) {
this.position = position;
}
public Matcher<View> getConstraints() {
return Matchers.allOf(new Matcher[]{
ViewMatchers.isAssignableFrom(RecyclerView.class), ViewMatchers.isDisplayed()
});
}
public String getDescription() {
return "scroll RecyclerView to position: " + this.position;
}
public void perform(UiController uiController, View view) {
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.scrollToPosition(this.position);
}
}
public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
return new RecyclerViewMatcher(recyclerViewId);
}
}

View file

@ -0,0 +1,128 @@
package com.anytypeio.anytype.test_utils.utils.espresso
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
class WithTextColor(private val expectedColor: Int) : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(item: TextView) = item.currentTextColor == expectedColor
override fun describeTo(description: Description) {
description.appendText("with text color:")
description.appendValue(expectedColor)
}
}
class WithBackgroundColor(private val expected: Int) : BoundedMatcher<View, View>(View::class.java) {
override fun describeTo(description: Description) {
description.appendText("with background color:")
description.appendValue(expected)
}
override fun matchesSafely(item: View): Boolean {
val actual = (item.background as ColorDrawable).color
return actual == expected
}
}
class WithoutBackgroundColor : BoundedMatcher<View, View>(View::class.java) {
override fun describeTo(description: Description) {
description.appendText("with background color:")
}
override fun matchesSafely(item: View): Boolean {
return item.background == null
}
}
class WithChildViewCount(private val expectedCount: Int) : BoundedMatcher<View, ViewGroup>(ViewGroup::class.java) {
override fun matchesSafely(item: ViewGroup): Boolean = item.childCount == expectedCount
override fun describeTo(description: Description) {
description.appendText("ViewGroup with child-count = $expectedCount");
}
}
class HasViewGroupChildViewWithText(private val pos: Int, val text: String) : BoundedMatcher<View, ViewGroup>(ViewGroup::class.java) {
private var actual: String? = null
override fun matchesSafely(item: ViewGroup): Boolean {
val child = item.getChildAt(pos)
checkNotNull(child) { throw IllegalStateException("No view child at position: $pos") }
check(child is TextView) { throw IllegalStateException("Child view is not text view at position: $pos, but: ${child::class.java.canonicalName}") }
actual = child.text.toString()
return actual == text
}
override fun describeTo(description: Description) {
if (actual != null) {
description.appendText("Should have text [$text] at position: $pos but was: $actual");
}
}
}
class HasChildViewWithText(private val pos: Int, val text: String, val target: Int) : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
private var actual: String? = null
override fun matchesSafely(item: RecyclerView): Boolean {
val holder = item.findViewHolderForLayoutPosition(pos)
checkNotNull(holder) { throw IllegalStateException("No holder at position: $pos") }
val target = holder.itemView.findViewById<TextView>(target)
actual = target.text.toString()
return actual == text
}
override fun describeTo(description: Description) {
if (actual != null) {
description.appendText("Should have text [$text] at position: $pos but was: $actual");
}
}
}
class WithTextColorRes(private val expectedColorRes: Int) : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(item: TextView): Boolean {
val color = ContextCompat.getColor(item.context, expectedColorRes)
return item.currentTextColor == color
}
override fun describeTo(description: Description) {
description.appendText("with text color:")
description.appendValue(expectedColorRes)
}
}
class TextLineCountMatcher(private val lines: Int) : TypeSafeMatcher<View>() {
override fun describeTo(description: Description?) {
description?.appendText("isTextInLines")
}
override fun matchesSafely(item: View?): Boolean {
return (item as TextView).lineCount == lines
}
}
class SetEditTextSelectionAction(private val selection: Int) : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(isDisplayed(), isAssignableFrom(EditText::class.java))
}
override fun getDescription(): String {
return "set selection to $selection"
}
override fun perform(uiController: UiController, view: View) {
(view as EditText).setSelection(selection)
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>