-
Notifications
You must be signed in to change notification settings - Fork 274
Add getAllByTestId and generic getBy and getAllBy queries #186
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
Conversation
acb01c7
to
107867e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, thanks for tackling this! Unfortunately, we don't want to add more queries and helpers other than getAllByTestId
currently. I described the issue you encountered in one of the comments, hope that's a bit clearer now.
export const queryByAPI = (instance: ReactTestInstance) => ({ | ||
queryBy: queryBy(instance), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this
queryByTestId: queryByTestId(instance), | ||
queryByName: queryByName(instance), | ||
queryByType: queryByType(instance), | ||
queryByText: queryByText(instance), | ||
queryByPlaceholder: queryByPlaceholder(instance), | ||
queryByProps: queryByProps(instance), | ||
queryAllBy: queryAllBy(instance), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this as well (and all occurrences)
@@ -173,13 +229,28 @@ export const getAllByProps = (instance: ReactTestInstance) => | |||
return results; | |||
}; | |||
|
|||
export const getAllByTestId = (instance: ReactTestInstance) => | |||
function getAllByTestIdFn(testID: string) { | |||
const results = instance.findAllByProps({ testID }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you noticed in the OP, because findAllByProps
returns all components with such ID (so every if the component have a testID
prop that is passed further and further, more than one component will be matched).
We want to filter the results to only match the element
s which element.type
is a string – this is the way we know the renderer rendered the "host node" (in case of RN, it's usually View
or Text
).
Something like this should be fine:
const results = instance.findAllByProps({ testID })
.filter(element => typeof element.type === 'string')
FYI, we're not currently doing this filtering for getAllByProps
et. al. queries, which is a bug. But we're removing these in v2 anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a terrible solution, it won't work if you put testID
on a custom component. My solution doesn't have that issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, this code:
const { getAllByTestId } = render(
<TouchableOpacity testID="testID" activeOpacity={0.5} />
)
console.log(getAllByTestId("testID").map(({ props, type }) => ({ props, type })))
Shows this:
[ { props: { testID: 'testID', activeOpacity: 0.5 },
type:
{ [Function]
displayName: 'TouchableOpacity',
propTypes: [Object],
getDefaultProps: [Function],
defaultProps: [Object] } },
{ props:
{ accessible: true,
accessibilityLabel: undefined,
accessibilityHint: undefined,
accessibilityRole: undefined,
accessibilityStates: undefined,
style: [Array],
nativeID: undefined,
testID: 'testID',
onLayout: undefined,
isTVSelectable: true,
hasTVPreferredFocus: undefined,
tvParallaxProperties: undefined,
hitSlop: undefined,
onStartShouldSetResponder: [Function],
onResponderTerminationRequest: [Function],
onResponderGrant: [Function],
onResponderMove: [Function],
onResponderRelease: [Function],
onResponderTerminate: [Function],
children: [Array] },
type:
{ [Function: AnimatedComponent]
__skipSetNativeProps_FOR_TESTS_ONLY: false,
propTypes: [Object] } },
{ props:
{ accessible: true,
accessibilityLabel: undefined,
accessibilityHint: undefined,
accessibilityRole: undefined,
accessibilityStates: undefined,
style: [Object],
nativeID: undefined,
testID: 'testID',
onLayout: undefined,
isTVSelectable: true,
hasTVPreferredFocus: undefined,
tvParallaxProperties: undefined,
hitSlop: undefined,
onStartShouldSetResponder: [Function],
onResponderTerminationRequest: [Function],
onResponderGrant: [Function],
onResponderMove: [Function],
onResponderRelease: [Function],
onResponderTerminate: [Function],
children: [Array],
collapsable: undefined },
type:
{ [Function: Component]
displayName: 'View',
'$$typeof': Symbol(react.forward_ref),
render: [Function: View] } },
{ props:
{ accessible: true,
style: [Object],
testID: 'testID',
isTVSelectable: true,
onStartShouldSetResponder: [Function],
onResponderTerminationRequest: [Function],
onResponderGrant: [Function],
onResponderMove: [Function],
onResponderRelease: [Function],
onResponderTerminate: [Function],
children: [Array] },
type: 'View' } ]
I don't think I need to explain why choosing the last element as the only one that the function should return is a terrible idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea behind testID
is to be applied to the element that directly translates to the native View (or other). If your component is implemented properly and passes props down to children, React should pass them to this very host element ("native" View). Firing events will also work, since they bubble up in the component hierarchy.
If you think it's terrible, feel free to close this PR and create your own fork of the library. I cannot simply accept hacks like generic getBy
and getByAll
helpers because the point of this library is to be opinionated and prevent you from accessing props wherever possible (that's why we e.g. remove getByProps
helpers in v2).
Summary
I don't know who put
getAllByTestId
andqueryAllByTestId
in the documentation when they don't actually exist, but that's a huge omission and I had to fix it. But that was easyThe bigger problem is that react test renderer acts idiotically when you try to search anything by props - for example, if you put one
Text
element with a giventestID
in your hierarchy and try to search by that ID, it returns two components, one which has the type equal to theText
component, and one for which the type is simply the string "Text" - it turns out that the internal hierarchy of react test renderer does not quite look like the one that you code. I don't have enough determination to find out why it is so, but I know that an easy way to fix this is to allow users to filter their searches by both testID and the component's type. Right now I can't do that easily with your package, I'd have to filter my components manually. So I created the generic queriesgetBy
andgetAllBy
, two which the programmer can pass two types of arguments:ReactTestInstance
, just like the one you would pass to the functionsfind()
andfindAll()
from react test renderer{ type: Text, props: { style: { color: 'red' } }, testID: 'label' }
will search for a text node which has color red in its style object and a "label" testID. Now,testID
doesn't actually exist on a test instance, so if you search by{ testID: 'something' }
it automatically gets converted to{ props: { testID: 'something' } }
.This is obviously a great feature and should be merged. Pozdrawiam.