Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to click a clickablespan using espresso?

Tags:

I have a textview with multiple clickable spans in it. I want to be able to test clicking these spans.

I tried setting up a custom ViewAction that would find the clickablespans in the TextView and then match their text with the desired text and then click on the xy coordinates of that text. However, it seems the spans added to the TextView aren't of type ClickableSpan and are instead the the fragment that added the span.

Therefore, I am not able to distinguish the link spans. Is there a better way to do this?

Adding the spans:

Util.addClickableSpan(spannableString, string, linkedString, new      ClickableSpan() {
@Override
public void onClick(View textView) {}
});

tvAcceptTc.setText(spannableString);
tvAcceptTc.setMovementMethod(LinkMovementMethod.getInstance());

Utility method:

public static void addClickableSpan(SpannableString spannableString,
                              String text,
                              String subText,
                              ClickableSpan clickableSpan) {
        int start = text.indexOf(subText);
        int end = text.indexOf(subText) + subText.length();
        int flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;

        spannableString.setSpan(clickableSpan, start, end, flags);
}

Defining the ViewAction:

@Override
        public void perform(UiController uiController, View view) {
            uiController.loopMainThreadUntilIdle();
            if (view instanceof TextView) {

                TextView textView = (TextView) view;
                Layout textViewLayout = textView.getLayout();


                SpannableString fullSpannable = new SpannableString(textView.getText());

                Object[] spans = fullSpannable.getSpans(0, fullSpannable.length(), Object.class);

                ClickableSpan span = null;
                for (Object object : spans) {
                    if (object instanceof BaseFragment) {
                        ClickableSpan foundSpan = (ClickableSpan)object;
                        int spanStart = fullSpannable.getSpanStart(foundSpan);
                        int spanEnd = fullSpannable.getSpanEnd(foundSpan);
                        if (fullSpannable.subSequence(spanStart, spanEnd).equals(aSubstring)) {
                            //Found the correct span!
                            span = foundSpan;
                        }
                    }
                } ... go on to click the xy-coordinates
like image 747
Captain Hammer Avatar asked Jul 11 '16 18:07

Captain Hammer


2 Answers

This is my solution. It's simpler because we don't need to find the coordinates. Once we have found the ClickableSpan, we just click on it:

public static ViewAction clickClickableSpan(final CharSequence textToClick) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return Matchers.instanceOf(TextView.class);
        }

        @Override
        public String getDescription() {
            return "clicking on a ClickableSpan";
        }

        @Override
        public void perform(UiController uiController, View view) {
            TextView textView = (TextView) view;
            SpannableString spannableString = (SpannableString) textView.getText();

            if (spannableString.length() == 0) {
                // TextView is empty, nothing to do
                throw new NoMatchingViewException.Builder()
                        .includeViewHierarchy(true)
                        .withRootView(textView)
                        .build();
            }

            // Get the links inside the TextView and check if we find textToClick
            ClickableSpan[] spans = spannableString.getSpans(0, spannableString.length(), ClickableSpan.class);
            if (spans.length > 0) {
                ClickableSpan spanCandidate;
                for (ClickableSpan span : spans) {
                    spanCandidate = span;
                    int start = spannableString.getSpanStart(spanCandidate);
                    int end = spannableString.getSpanEnd(spanCandidate);
                    CharSequence sequence = spannableString.subSequence(start, end);
                    if (textToClick.toString().equals(sequence.toString())) {
                        span.onClick(textView);
                        return;
                    }
                }
            }

            // textToClick not found in TextView
            throw new NoMatchingViewException.Builder()
                    .includeViewHierarchy(true)
                    .withRootView(textView)
                    .build();

        }
    };
}

Now you can use our custom ViewAction just like that:

    onView(withId(R.id.myTextView)).perform(clickClickableSpan("myLink"));
like image 63
FOMDeveloper Avatar answered Sep 23 '22 17:09

FOMDeveloper


Here is the Kotlin version of accepted answer

fun clickClickableSpan(textToClick: CharSequence): ViewAction {
    return object : ViewAction {

        override fun getConstraints(): Matcher<View> {
            return Matchers.instanceOf(TextView::class.java)
        }

        override fun getDescription(): String {
            return "clicking on a ClickableSpan";
        }

        override fun perform(uiController: UiController, view: View) {
            val textView = view as TextView
            val spannableString = textView.text as SpannableString

            if (spannableString.isEmpty()) {
                // TextView is empty, nothing to do
                throw NoMatchingViewException.Builder()
                        .includeViewHierarchy(true)
                        .withRootView(textView)
                        .build();
            }

            // Get the links inside the TextView and check if we find textToClick
            val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
            if (spans.isNotEmpty()) {
                var spanCandidate: ClickableSpan
                for (span: ClickableSpan in spans) {
                    spanCandidate = span
                    val start = spannableString.getSpanStart(spanCandidate)
                    val end = spannableString.getSpanEnd(spanCandidate)
                    val sequence = spannableString.subSequence(start, end)
                    if (textToClick.toString().equals(sequence.toString())) {
                        span.onClick(textView)
                        return;
                    }
                }
            }

            // textToClick not found in TextView
            throw NoMatchingViewException.Builder()
                    .includeViewHierarchy(true)
                    .withRootView(textView)
                    .build()

        }
    }
} 
like image 37
Rupak Samant Avatar answered Sep 23 '22 17:09

Rupak Samant