Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why are accessibilty elements only text, textInput, and switch? #1566

Closed
KrastanD opened this issue Feb 22, 2024 · 13 comments
Closed

Why are accessibilty elements only text, textInput, and switch? #1566

KrastanD opened this issue Feb 22, 2024 · 13 comments
Labels
question Further information is requested

Comments

@KrastanD
Copy link
Contributor

Ask your Question

I am using Tamagui and I want to check that a radio button has been checked but it's not able to and I think it's because Tamagui adds the radio role to a view but React Native Testing Library only checks text, textInput and switch elements for a role.

Could you explain the reasoning for having only those elements as accessibility elements?
Do you have a recommended way that I can check that the radio button has been checked, or does Tamagui have to change how they set role?

@KrastanD KrastanD added the question Further information is requested label Feb 22, 2024
@mdjastrzebski
Copy link
Member

mdjastrzebski commented Feb 22, 2024

To make a View an accessibility element just pass accessible={true} props to it.

The elements types you mention: Text, TextInput and Switch are considered to be accessible by default (even without accessible={true} prop).

Not sure what happens in Tamagui, as the code is pretty abstract. If you think that RNTL is missing something, the create a minimal Expo app using Tamagui that showcases the missing case. Having something like this at hand would allow me verify whether to tweak our simulation code.

@KrastanD
Copy link
Contributor Author

Adding accessible did make the element selectable, thank you!!! Idk how I missed that line.

I created an example expo app, but my test using userEvent wasn't checking the component. I tried all kinds of selectors but that didn't make a difference. Out of desperation I tried using fireEvent and that worked. Could you take a look?

My hunch (from my understanding) is that fireEvent checks the parents handlers until it finds one, and userEvent doesn't do that so here, the onValueChange is on the RadioGroup level, not the RadioGroupItem so userEvent can't trigger it. I'm not sure how to trigger it with userEvent since I need to click on the specific item.

@tlmader
Copy link

tlmader commented Mar 1, 2024

Just ran into this myself! I believe Tamagui should handle adding accessible to components, right? React Native Elements takes care of this for you.

@mdjastrzebski
Copy link
Member

@KrastanD User Event has code code that performs lookup for pressable element from given element up:

const hostParentElement = getHostParent(element);

From that point it locks on that element, and tries to send all of touch events there without further lookups.

@mdjastrzebski
Copy link
Member

mdjastrzebski commented Apr 26, 2024

@KrastanD I've reviewed this issue to check if it can be closed as it looked stale. I've run your repro repository and found it interesting. When I run screen.debug() on our failing test, it does not seems to contain onPress or similar event handler:

<View>
      <View
        __scopeRovingFocusGroup="RadioGroup"
        loop={true}
      >
        <View
          accessibilityRole="radiogroup"
        >
          <View>
            <View
              __scopeRovingFocusGroup="RadioGroup"
              accessibilityRole="radio"
              accessibilityState={
                {
                  "checked": false,
                }
              }
              accessible={true}
              active={false}
              focusable={true}
              id="radiogroup-1"
            />
            <View
              pointerEvents="none"
            />
            <Text
              htmlFor="radiogroup-1"
              id=":r3:"
              suppressHighlighting={true}
              userSelect="none"
            >
              First value
            </Text>
          </View>
          <View>
            <View
              __scopeRovingFocusGroup="RadioGroup"
              accessibilityRole="radio"
              accessibilityState={
                {
                  "checked": false,
                }
              }
              accessible={true}
              active={false}
              focusable={true}
              id="radiogroup-2"
            />
            <View
              pointerEvents="none"
            />
            <Text
              htmlFor="radiogroup-2"
              id=":r4:"
              suppressHighlighting={true}
              userSelect="none"
            >
              Second value
            </Text>
          </View>
          <View>
            <View
              __scopeRovingFocusGroup="RadioGroup"
              accessibilityRole="radio"
              accessibilityState={
                {
                  "checked": false,
                }
              }
              accessible={true}
              active={false}
              focusable={true}
              id="radiogroup-3"
            />
            <View
              pointerEvents="none"
            />
            <Text
              htmlFor="radiogroup-3"
              id=":r5:"
              suppressHighlighting={true}
              userSelect="none"
            >
              Third value
            </Text>
          </View>
        </View>
      </View>
    </View>

screen.debug displays only host views, and it seems that none of these views actually has any event handler 🧐 This will make userEvent.press fail as it expects onPress or onPressIn/onPressOut to be present. The reason why Fire Event works is that it invokes event handlers on both host and composite components, and Tamagui's RadioGroup seems to contain such onPress handler on RadioGroupItemFrame.

Tamagui is quite complex as it uses some additional abstractions, so it's hard for me to say what exactly is happening there. The thing that I can see, is that the host element tree does not seem to contain any event handlers (onXxx) at all. Perhaps this is supposed to be selected by the following effect code in React Native 🤷‍♂️

Let me know if you are willing to explore this more or should we close this issue.

@mfrfinbox
Copy link

I am on the exact same situation, after hours of research I found that using the accesible attribute works fine and I'm thinking on adding a wrapper for all these elements from Tamagui that need it.

I wasn't very happy but I think that's the only solution, then I found that only fireEvent works instead of user event.

My concern is, can we rely on all these and be in peace with having the wrapper plus using fire event? It's messing with my head a little bit since I have the feeling that fireEvent will be away eventually, perhaps I'm wrong on that.

Thanks

@mdjastrzebski
Copy link
Member

@mfrfinbox you should raise this issue with Tamagui maintainers. Their code is pretty complex due to abstractions and RN/web shared code, so they should be the best people to consult on this.

From my perspective the issue is on Tamagui's side (lack of accessible prop) but I would be happy to be proven wrong 😊

@KrastanD
Copy link
Contributor Author

KrastanD commented May 1, 2024

For the accessible={true}, that should be contributed to tamagui directly imo so everyone gets it out the box and don't run into it like @mfrfinbox and I have.

As for the host elements not showing onPress, I'm not quite sure how that happens. Aren't all composite elements built from host elements? The RadioGroupItemFrame has the onPress and extends a ThemableStack, which extends a YStack, which extends a View.

@mfrfinbox
Copy link

I've open the issues on the Tamagui side, thanks

tamagui/tamagui#2613
tamagui/tamagui#2614

@mdjastrzebski
Copy link
Member

@mfrfinbox thank's for logging the issues, please update them with MyComponent source, as otherwise they are incomplete.

@mdjastrzebski
Copy link
Member

mdjastrzebski commented May 2, 2024

@KrastanD as for host elements, in React the components are not being extended (subclassed) in classic OOP way, they are being rendered (composed) by other (composite) components.
The YStack is a composite component that renders View, and should pass all it's props there as it uses styled, however for some reasons onPress from RadioGroupItemFrame does not get to the final View host element.

The reason why User Events insist on invoking only events on the host components, is that these are the only components that the user can see as they correspond to UI controls in iOS/Android. Composite components are just an abstraction, and cannot be directly observed or interacted by the users.

@mfrfinbox
Copy link

@mfrfinbox thank's for logging the issues, please update them with MyComponent source, as otherwise they are incomplete.

Done!

@mdjastrzebski
Copy link
Member

Closing as it requires fixing upstream (Tamagui)

@mdjastrzebski mdjastrzebski closed this as not planned Won't fix, can't repro, duplicate, stale May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants