mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
support 'scope_id' for set_variable actions
commit_hash:86fd118ac6ef2eed66a544a5c95f4e367e548f44
This commit is contained in:
@@ -1111,6 +1111,7 @@
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/ShadowCache.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/ShadowCache.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/SightActionIsEnabledObserver.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/SightActionIsEnabledObserver.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/ViewBindingProvider.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/ViewBindingProvider.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/ViewLocator.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/ViewLocator.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/ViewVisibilityCalculator.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/ViewVisibilityCalculator.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparator.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparator.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparatorReporter.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparatorReporter.kt",
|
||||
@@ -1460,6 +1461,7 @@
|
||||
"client/android/divkit-demo-app/lint-baseline.xml":"divkit/public/client/android/divkit-demo-app/lint-baseline.xml",
|
||||
"client/android/divkit-demo-app/proguard-rules.pro":"divkit/public/client/android/divkit-demo-app/proguard-rules.pro",
|
||||
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/AtStartHistogramsTest.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/AtStartHistogramsTest.kt",
|
||||
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivActionTest.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivActionTest.kt",
|
||||
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivCollectionAdapterTest.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivCollectionAdapterTest.kt",
|
||||
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivCustomTest.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivCustomTest.kt",
|
||||
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivFocusableInputTest.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/DivFocusableInputTest.kt",
|
||||
@@ -18072,6 +18074,9 @@
|
||||
"test_data/tutorials/counter.json":"divkit/public/test_data/tutorials/counter.json",
|
||||
"test_data/tutorials/stateful_card.json":"divkit/public/test_data/tutorials/stateful_card.json",
|
||||
"test_data/tutorials/ticking_timer.json":"divkit/public/test_data/tutorials/ticking_timer.json",
|
||||
"test_data/ui_test_data/actions/scoped_actions.json":"divkit/public/test_data/ui_test_data/actions/scoped_actions.json",
|
||||
"test_data/ui_test_data/actions/scoped_actions_ambiguous_scope.json":"divkit/public/test_data/ui_test_data/actions/scoped_actions_ambiguous_scope.json",
|
||||
"test_data/ui_test_data/actions/scoped_actions_nested_scope.json":"divkit/public/test_data/ui_test_data/actions/scoped_actions_nested_scope.json",
|
||||
"test_data/ui_test_data/custom/div_custom_several_states_changing.json":"divkit/public/test_data/ui_test_data/custom/div_custom_several_states_changing.json",
|
||||
"test_data/ui_test_data/custom/div_custom_state_changing.json":"divkit/public/test_data/ui_test_data/custom/div_custom_state_changing.json",
|
||||
"test_data/ui_test_data/focus/actions.json":"divkit/public/test_data/ui_test_data/focus/actions.json",
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.yandex.div.core;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.yandex.div.core.actions.DivActionTypedHandlerProxy;
|
||||
import com.yandex.div.core.annotations.PublicApi;
|
||||
import com.yandex.div.core.downloader.DivDownloadActionHandler;
|
||||
import com.yandex.div.core.expression.storedvalues.StoredValuesActionHandler;
|
||||
import com.yandex.div.core.state.DivStatePath;
|
||||
import com.yandex.div.core.state.PathFormatException;
|
||||
import com.yandex.div.core.view2.BindingContext;
|
||||
import com.yandex.div.core.view2.Div2View;
|
||||
import com.yandex.div.core.view2.ViewLocator;
|
||||
import com.yandex.div.core.view2.divs.widgets.DivHolderView;
|
||||
import com.yandex.div.core.view2.items.DivItemChangeActionHandler;
|
||||
import com.yandex.div.data.VariableMutationException;
|
||||
import com.yandex.div.internal.Assert;
|
||||
@@ -43,6 +46,8 @@ public class DivActionHandler {
|
||||
public static final String TIMER = "timer";
|
||||
public static final String TRIGGER = "trigger";
|
||||
public static final String VIDEO = "video";
|
||||
public static final String ANIMATION_END = "animation_end";
|
||||
public static final String ANIMATION_CANCEL = "animation_cancel";
|
||||
}
|
||||
|
||||
private static final String SCHEME_DIV_ACTION = "div-action";
|
||||
@@ -95,14 +100,17 @@ public class DivActionHandler {
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
if (DivActionTypedHandlerProxy.handleAction(action, view, resolver)) {
|
||||
ExpressionResolver scopedResolver = findExpressionResolverById((Div2View) view, action.scopeId);
|
||||
ExpressionResolver localResolver = scopedResolver == null ? resolver : scopedResolver;
|
||||
|
||||
if (DivActionTypedHandlerProxy.handleAction(action, view, localResolver)) {
|
||||
return true;
|
||||
}
|
||||
Uri url = action.url != null ? action.url.evaluate(resolver) : null;
|
||||
if (DivDownloadActionHandler.canHandle(url, view)) {
|
||||
return DivDownloadActionHandler.handleAction(action, (Div2View) view, resolver);
|
||||
return DivDownloadActionHandler.handleAction(action, (Div2View) view, localResolver);
|
||||
}
|
||||
return handleActionUrl(url, view, resolver);
|
||||
return handleAction(action.scopeId, url, view, localResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,14 +228,17 @@ public class DivActionHandler {
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
if (DivActionTypedHandlerProxy.handleVisibilityAction(action, view, resolver)) {
|
||||
ExpressionResolver scopedResolver = findExpressionResolverById((Div2View) view, action.getScopeId());
|
||||
ExpressionResolver localResolver = scopedResolver == null ? resolver : scopedResolver;
|
||||
|
||||
if (DivActionTypedHandlerProxy.handleVisibilityAction(action, view, localResolver)) {
|
||||
return true;
|
||||
}
|
||||
Uri url = action.getUrl() != null ? action.getUrl().evaluate(resolver) : null;
|
||||
if (DivDownloadActionHandler.canHandle(url, view)) {
|
||||
return DivDownloadActionHandler.handleVisibilityAction(action, (Div2View) view, resolver);
|
||||
return DivDownloadActionHandler.handleVisibilityAction(action, (Div2View) view, localResolver);
|
||||
}
|
||||
return handleActionUrl(url, view, resolver);
|
||||
return handleAction(action.getScopeId(), url, view, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,6 +336,35 @@ public class DivActionHandler {
|
||||
@Nullable Uri uri,
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
return handleActionUrl(null, uri, view, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the URI with {@code div-action} scheme.
|
||||
*
|
||||
* @param scopeId id of div that denotes scope of given action
|
||||
* @param uri URI to handle
|
||||
* @param view calling DivView
|
||||
* @param resolver resolver for current action
|
||||
* @return TRUE if uri was handled
|
||||
*/
|
||||
public final boolean handleActionUrl(
|
||||
@Nullable String scopeId,
|
||||
@Nullable Uri uri,
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
ExpressionResolver scopedResolver = findExpressionResolverById((Div2View) view, scopeId);
|
||||
ExpressionResolver localResolver = scopedResolver == null ? resolver : scopedResolver;
|
||||
return handleAction(scopeId, uri, view, localResolver);
|
||||
}
|
||||
|
||||
private boolean handleAction(
|
||||
@Nullable String scopeId,
|
||||
@Nullable Uri uri,
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
@@ -332,13 +372,18 @@ public class DivActionHandler {
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (SCHEME_DIV_ACTION.equals(uri.getScheme())) {
|
||||
return handleAction(uri, view, resolver);
|
||||
return handleActionInternal(scopeId, uri, view, resolver);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleAction(@NonNull Uri uri, @NonNull DivViewFacade view, @NonNull ExpressionResolver resolver) {
|
||||
private boolean handleActionInternal(
|
||||
@Nullable String scopeId,
|
||||
@Nullable Uri uri,
|
||||
@NonNull DivViewFacade view,
|
||||
@NonNull ExpressionResolver resolver
|
||||
) {
|
||||
String action = uri.getAuthority();
|
||||
if (AUTHORITY_SWITCH_STATE.equals(action)) {
|
||||
String stateId = uri.getQueryParameter(PARAM_STATE_ID);
|
||||
@@ -447,4 +492,20 @@ public class DivActionHandler {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ExpressionResolver findExpressionResolverById(Div2View divView, @Nullable String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
View targetView = ViewLocator.findViewWithTag(divView, id);
|
||||
if (targetView instanceof DivHolderView) {
|
||||
BindingContext bindingContext = ((DivHolderView<?>) targetView).getBindingContext();
|
||||
if (bindingContext != null) {
|
||||
return bindingContext.getExpressionResolver();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ internal class DivActionTypedArrayMutationHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver,
|
||||
|
||||
+7
-1
@@ -8,7 +8,13 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DivActionTypedClearFocusHandler @Inject constructor() : DivActionTypedHandler {
|
||||
override fun handleAction(action: DivActionTyped, view: Div2View, resolver: ExpressionResolver): Boolean {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
): Boolean {
|
||||
return when (action) {
|
||||
is DivActionTyped.ClearFocus -> {
|
||||
view.clearFocus()
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ internal class DivActionTypedCopyToClipboardHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver,
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ internal class DivActionTypedDictSetValueHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver,
|
||||
|
||||
+7
-1
@@ -12,7 +12,13 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DivActionTypedFocusElementHandler @Inject constructor() : DivActionTypedHandler {
|
||||
override fun handleAction(action: DivActionTyped, view: Div2View, resolver: ExpressionResolver): Boolean {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
): Boolean {
|
||||
return when (action) {
|
||||
is DivActionTyped.FocusElement -> {
|
||||
handleRequestFocus(action.value, view, resolver)
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@ import com.yandex.div2.DivActionTyped
|
||||
|
||||
internal interface DivActionTypedHandler {
|
||||
|
||||
fun handleAction(action: DivActionTyped, view: Div2View, resolver: ExpressionResolver): Boolean
|
||||
fun handleAction(scopeId: String?, action: DivActionTyped, view: Div2View, resolver: ExpressionResolver): Boolean
|
||||
}
|
||||
|
||||
+7
-2
@@ -12,8 +12,13 @@ internal class DivActionTypedHandlerCombiner @Inject constructor(
|
||||
private val handlers: Set<@JvmSuppressWildcards DivActionTypedHandler>
|
||||
) {
|
||||
|
||||
fun handleAction(action: DivActionTyped, div2View: Div2View, resolver: ExpressionResolver): Boolean {
|
||||
val wasHandled = handlers.find { it.handleAction(action, div2View, resolver) } != null
|
||||
fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
div2View: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
): Boolean {
|
||||
val wasHandled = handlers.find { it.handleAction(scopeId, action, div2View, resolver) } != null
|
||||
if (!wasHandled) {
|
||||
KLog.d(TAG) { "Unexpected ${action::class.java} was not handled" }
|
||||
}
|
||||
|
||||
+5
-3
@@ -14,15 +14,16 @@ internal object DivActionTypedHandlerProxy {
|
||||
|
||||
@JvmStatic
|
||||
fun handleVisibilityAction(action: DivSightAction, view: DivViewFacade, resolver: ExpressionResolver): Boolean {
|
||||
return handleAction(action.typed, view, resolver, action.downloadCallbacks)
|
||||
return handleAction(action.scopeId, action.typed, view, resolver, action.downloadCallbacks)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleAction(action: DivAction, view: DivViewFacade, resolver: ExpressionResolver): Boolean {
|
||||
return handleAction(action.typed, view, resolver, action.downloadCallbacks)
|
||||
return handleAction(action.scopeId, action.typed, view, resolver, action.downloadCallbacks)
|
||||
}
|
||||
|
||||
private fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped?,
|
||||
view: DivViewFacade,
|
||||
resolver: ExpressionResolver,
|
||||
@@ -38,6 +39,7 @@ internal object DivActionTypedHandlerProxy {
|
||||
if (action is DivActionTyped.Download) {
|
||||
return DivDownloadActionHandler.handleAction(action.value, downloadCallbacks, view, resolver)
|
||||
}
|
||||
return view.div2Component.actionTypedHandlerCombiner.handleAction(action, view, resolver)
|
||||
return view.div2Component.actionTypedHandlerCombiner.handleAction(scopeId, action, view, resolver)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,10 +8,11 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DivActionTypedHideTooltipHandler @Inject constructor()
|
||||
internal class DivActionTypedHideTooltipHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ internal class DivActionTypedSetStateHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ internal class DivActionTypedSetVariableHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver,
|
||||
|
||||
+2
-1
@@ -8,10 +8,11 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DivActionTypedShowTooltipHandler @Inject constructor()
|
||||
internal class DivActionTypedShowTooltipHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
|
||||
+2
-1
@@ -8,9 +8,10 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DivActionTypedTimerHandler @Inject constructor()
|
||||
internal class DivActionTypedTimerHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
|
||||
+2
-1
@@ -8,10 +8,11 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DivActionTypedVideoHandler @Inject constructor()
|
||||
internal class DivActionTypedVideoHandler @Inject constructor()
|
||||
: DivActionTypedHandler {
|
||||
|
||||
override fun handleAction(
|
||||
scopeId: String?,
|
||||
action: DivActionTyped,
|
||||
view: Div2View,
|
||||
resolver: ExpressionResolver
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.yandex.div.core.view2
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.yandex.div.core.actions.logError
|
||||
|
||||
internal object ViewLocator {
|
||||
|
||||
@JvmStatic
|
||||
fun findViewWithTag(divView: Div2View, tag: String): View? {
|
||||
val foundViews = divView.view.findViewsWithTag(tag)
|
||||
if (foundViews.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
if (foundViews.size > 1) {
|
||||
divView.logError(RuntimeException("Ambiguous scope id. There are ${foundViews.size} divs with id '$tag'"))
|
||||
return null
|
||||
}
|
||||
return foundViews.first()
|
||||
}
|
||||
|
||||
private fun View.findViewsWithTag(tag: Any?): List<View> {
|
||||
if (tag == null) return emptyList()
|
||||
val result = mutableListOf<View>()
|
||||
findViewsWithTagTraversal(this, tag, result)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findViewsWithTagTraversal(view: View, tag: Any, views: MutableList<View>): List<View> {
|
||||
if (tag == view.tag) {
|
||||
views += view
|
||||
}
|
||||
|
||||
if (view is ViewGroup) {
|
||||
for (i in 0 until view.childCount) {
|
||||
findViewsWithTagTraversal(view.getChildAt(i), tag, views)
|
||||
}
|
||||
}
|
||||
return views
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.yandex.div
|
||||
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.IdlingResource
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.yandex.div.rule.uiTestRule
|
||||
import com.yandex.div.steps.DivViewInteractions.viewWithTag
|
||||
import com.yandex.div.steps.DivViewInteractions.viewWithTagAndText
|
||||
import com.yandex.div.steps.divView
|
||||
import com.yandex.div.view.isDisplayed
|
||||
import com.yandex.divkit.demo.DummyActivity
|
||||
import com.yandex.divkit.demo.div.DemoDiv2Logger
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DivActionTest {
|
||||
|
||||
private val activityRule = ActivityTestRule(DummyActivity::class.java)
|
||||
|
||||
@get:Rule
|
||||
val rule = uiTestRule { activityRule }
|
||||
|
||||
@Before
|
||||
fun clearLogs() {
|
||||
DemoDiv2Logger.clearLogActions()
|
||||
}
|
||||
|
||||
@After
|
||||
fun unregisterAllResources() {
|
||||
val resources: Collection<IdlingResource> = IdlingRegistry.getInstance().resources
|
||||
if (!resources.isEmpty()) {
|
||||
resources.forEach { IdlingRegistry.getInstance().unregister(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun scopedActionChangesLocalVariable() {
|
||||
divView {
|
||||
testAsset = "ui_test_data/actions/scoped_actions.json"
|
||||
activityRule.buildContainer(MATCH_PARENT, WRAP_CONTENT)
|
||||
|
||||
tapOnText("Scoped")
|
||||
|
||||
assert {
|
||||
viewWithTag(tag = "title").checkHasText(text = "Title has been changed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notScopedActionDoesNotChangeLocalVariable() {
|
||||
divView {
|
||||
testAsset = "ui_test_data/actions/scoped_actions.json"
|
||||
activityRule.buildContainer(MATCH_PARENT, WRAP_CONTENT)
|
||||
|
||||
tapOnText("Not scoped")
|
||||
|
||||
assert {
|
||||
viewWithTag(tag = "title").checkHasText(text = "Lorem ipsum")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun scopedActionChangesLocalVariable_fromNestedScope() {
|
||||
divView {
|
||||
testAsset = "ui_test_data/actions/scoped_actions_nested_scope.json"
|
||||
activityRule.buildContainer(MATCH_PARENT, WRAP_CONTENT)
|
||||
|
||||
tapOnText("Content scope")
|
||||
|
||||
assert {
|
||||
viewWithTag(tag = "title").checkHasText(text = "Title has been changed from 'content' scope")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun scopedActionDoesNotChangeLocalVariable_whenScopeIsAmbiguous() {
|
||||
divView {
|
||||
testAsset = "ui_test_data/actions/scoped_actions_ambiguous_scope.json"
|
||||
activityRule.buildContainer(MATCH_PARENT, WRAP_CONTENT)
|
||||
|
||||
tapOnText("Ambiguous scope")
|
||||
|
||||
assert {
|
||||
viewWithTagAndText(tag = "some_text", text = "Lorem ipsum").isDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -1,8 +1,21 @@
|
||||
package com.yandex.div.steps
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import com.yandex.div.core.Div2Context
|
||||
import com.yandex.div.core.view2.Div2View
|
||||
import com.yandex.test.util.Report.step
|
||||
import com.yandex.test.util.StepsDsl
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.Matchers.`is`
|
||||
|
||||
private val divViewMatcher = isAssignableFrom(Div2View::class.java)
|
||||
|
||||
internal fun divView(block: DivViewSteps.() -> Unit) = block(DivViewSteps())
|
||||
|
||||
@@ -28,4 +41,29 @@ class DivViewSteps: DivTestAssetSteps() {
|
||||
fun detachFromParent() = runOnMainSync {
|
||||
container.removeView(div2View)
|
||||
}
|
||||
|
||||
fun tapOnText(text: String): Unit = step("Click on text '$text'") {
|
||||
onView(withText(text)).perform(click())
|
||||
}
|
||||
|
||||
fun assert(f: DivViewAssertions.() -> Unit) = f(DivViewAssertions())
|
||||
}
|
||||
|
||||
object DivViewInteractions {
|
||||
|
||||
fun viewWithTag(tag: String): ViewInteraction {
|
||||
return onView(withTagValue(`is`(tag)))
|
||||
}
|
||||
|
||||
fun viewWithTagAndText(tag: String, text: String): ViewInteraction {
|
||||
return onView(allOf(withTagValue(`is`(tag)), withText(text)))
|
||||
}
|
||||
}
|
||||
|
||||
@StepsDsl
|
||||
class DivViewAssertions {
|
||||
|
||||
fun ViewInteraction.checkHasText(text: String): Unit = step("Assert view has text '$text'") {
|
||||
check(ViewAssertions.matches(withText(text)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"templates": {
|
||||
"title": {
|
||||
"type": "text",
|
||||
"font_size": 24,
|
||||
"font_weight": "bold",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#586E75"
|
||||
},
|
||||
"base_text": {
|
||||
"type": "text",
|
||||
"font_size": 18,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#002B36"
|
||||
},
|
||||
"button": {
|
||||
"type": "text",
|
||||
"height": {
|
||||
"type": "fixed",
|
||||
"value": 48
|
||||
},
|
||||
"paddings": {
|
||||
"left": 8,
|
||||
"top": 8,
|
||||
"right": 8,
|
||||
"bottom": 8
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"$color": "background_color"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"font_size": 20,
|
||||
"font_weight": "medium",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#FDF6E3"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "scoped_actions",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"orientation": "vertical",
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 16,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#EEE8D5"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"type": "title",
|
||||
"id": "title",
|
||||
"text": "@{title_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "title_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "base_text",
|
||||
"id": "content",
|
||||
"text": "@{content_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "content_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"orientation": "horizontal",
|
||||
"items": [
|
||||
{
|
||||
"type": "button",
|
||||
"margins": {
|
||||
"right": 16
|
||||
},
|
||||
"background_color": "#2AA198",
|
||||
"text": "Scoped",
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "change_title",
|
||||
"scope_id": "title",
|
||||
"url": "div-action://set_variable?name=title_text&value=Title%20has%20been%20changed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"background_color": "#B58900",
|
||||
"text": "Not scoped",
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "change_title",
|
||||
"url": "div-action://set_variable?name=title_text&value=Title%20has%20been%20changed"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"templates": {
|
||||
"title": {
|
||||
"type": "text",
|
||||
"font_size": 24,
|
||||
"font_weight": "bold",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#586E75"
|
||||
},
|
||||
"base_text": {
|
||||
"type": "text",
|
||||
"font_size": 18,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#002B36"
|
||||
},
|
||||
"button": {
|
||||
"type": "text",
|
||||
"height": {
|
||||
"type": "fixed",
|
||||
"value": 48
|
||||
},
|
||||
"paddings": {
|
||||
"left": 8,
|
||||
"top": 8,
|
||||
"right": 8,
|
||||
"bottom": 8
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"$color": "background_color"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"font_size": 20,
|
||||
"font_weight": "medium",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#FDF6E3"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "scoped_actions_ambiguous_scope",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"id": "title",
|
||||
"orientation": "vertical",
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 16,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#EEE8D5"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"type": "title",
|
||||
"id": "title",
|
||||
"text": "@{title_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "title_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "base_text",
|
||||
"id": "content",
|
||||
"text": "@{content_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "content_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"background_color": "#B58900",
|
||||
"text": "Ambiguous scope",
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "change_some_text",
|
||||
"scope_id": "title",
|
||||
"url": "div-action://set_variable?name=title_text&value=Title%20has%20been%20changed"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"templates": {
|
||||
"title": {
|
||||
"type": "text",
|
||||
"font_size": 24,
|
||||
"font_weight": "bold",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#586E75"
|
||||
},
|
||||
"base_text": {
|
||||
"type": "text",
|
||||
"font_size": 18,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#002B36"
|
||||
},
|
||||
"button": {
|
||||
"type": "text",
|
||||
"height": {
|
||||
"type": "fixed",
|
||||
"value": 48
|
||||
},
|
||||
"paddings": {
|
||||
"left": 8,
|
||||
"top": 8,
|
||||
"right": 8,
|
||||
"bottom": 8
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"$color": "background_color"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"font_size": 20,
|
||||
"font_weight": "medium",
|
||||
"text_alignment_horizontal": "center",
|
||||
"text_alignment_vertical": "center",
|
||||
"text_color": "#FDF6E3"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "scoped_actions_nested_scope",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"orientation": "vertical",
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 16,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#EEE8D5"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"type": "title",
|
||||
"id": "title",
|
||||
"text": "@{title_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "base_text",
|
||||
"id": "content",
|
||||
"text": "@{content_text}",
|
||||
"margins": {
|
||||
"bottom": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"orientation": "horizontal",
|
||||
"items": [
|
||||
{
|
||||
"type": "button",
|
||||
"margins": {
|
||||
"right": 16
|
||||
},
|
||||
"background_color": "#2AA198",
|
||||
"text": "Title scope",
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "change_title",
|
||||
"scope_id": "title",
|
||||
"url": "div-action://set_variable?name=title_text&value=Title%20has%20been%20changed%20from%20'title'%20scope"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"background_color": "#268BD2",
|
||||
"text": "Content scope",
|
||||
"actions": [
|
||||
{
|
||||
"log_id": "change_title",
|
||||
"scope_id": "content",
|
||||
"url": "div-action://set_variable?name=title_text&value=Title%20has%20been%20changed%20from%20'content'%20scope"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"name": "title_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum"
|
||||
},
|
||||
{
|
||||
"name": "content_text",
|
||||
"type": "string",
|
||||
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user