-
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
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// @flow | ||
import React from 'react'; | ||
import { TouchableOpacity, Text } from 'react-native'; | ||
import { render } from '..'; | ||
|
||
const BUTTON_ID = 'button_id'; | ||
const TEXT_ID = 'text_id'; | ||
const BUTTON_STYLE = { textTransform: 'uppercase' }; | ||
const TEXT_LABEL = 'cool text'; | ||
const NO_MATCHES_TEXT = 'not-existent-element'; | ||
|
||
const NO_INSTANCES_FOUND = 'No instances found'; | ||
const FOUND_TWO_INSTANCES = 'Expected 1 but found 2 instances'; | ||
|
||
const Typography = ({ children, ...rest }) => { | ||
return <Text {...rest}>{children}</Text>; | ||
}; | ||
|
||
class Button extends React.Component<*> { | ||
render() { | ||
return ( | ||
<TouchableOpacity testID={BUTTON_ID} style={BUTTON_STYLE}> | ||
<Typography testID={TEXT_ID}>{this.props.children}</Typography> | ||
</TouchableOpacity> | ||
); | ||
} | ||
} | ||
|
||
function Section() { | ||
return ( | ||
<> | ||
<Typography testID={TEXT_ID}>Title</Typography> | ||
<Button>{TEXT_LABEL}</Button> | ||
</> | ||
); | ||
} | ||
|
||
test('getBy, queryBy', () => { | ||
const { getBy, queryBy } = render(<Section />); | ||
|
||
expect(getBy({ testID: BUTTON_ID }).props.testID).toEqual(BUTTON_ID); | ||
const button = getBy({ testID: /button/g }); | ||
expect(button && button.props.testID).toEqual(BUTTON_ID); | ||
expect( | ||
getBy({ testID: BUTTON_ID, type: TouchableOpacity }).props.testID | ||
).toEqual(BUTTON_ID); | ||
expect(() => getBy({ testID: BUTTON_ID, type: Text })).toThrow( | ||
NO_INSTANCES_FOUND | ||
); | ||
expect( | ||
getBy({ | ||
testID: BUTTON_ID, | ||
props: { style: { textTransform: 'uppercase' } }, | ||
}).props.testID | ||
).toEqual(BUTTON_ID); | ||
expect(() => getBy({ testID: NO_MATCHES_TEXT })).toThrow(NO_INSTANCES_FOUND); | ||
expect(queryBy({ testID: NO_MATCHES_TEXT })).toBeNull(); | ||
|
||
expect(() => getBy({ testID: TEXT_ID })).toThrow(FOUND_TWO_INSTANCES); | ||
expect(() => queryBy({ testID: TEXT_ID })).toThrow(FOUND_TWO_INSTANCES); | ||
}); | ||
|
||
test('getAllBy, queryAllBy', () => { | ||
const { getAllBy, queryAllBy } = render(<Section />); | ||
|
||
const texts = getAllBy({ testID: TEXT_ID, type: Text }); | ||
expect(texts).toHaveLength(2); | ||
const buttons = getAllBy({ testID: BUTTON_ID, type: TouchableOpacity }); | ||
expect(buttons).toHaveLength(1); | ||
expect(queryAllBy({ testID: /id/g })).toEqual( | ||
expect.arrayContaining([...texts, ...buttons]) | ||
); | ||
expect(() => getAllBy({ testID: NO_MATCHES_TEXT })).toThrow( | ||
NO_INSTANCES_FOUND | ||
); | ||
expect(queryAllBy({ testID: NO_MATCHES_TEXT })).toEqual([]); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
// @flow | ||
import * as React from 'react'; | ||
import { | ||
getBy, | ||
getByTestId, | ||
getByName, | ||
getByType, | ||
getByText, | ||
getByPlaceholder, | ||
getByProps, | ||
getAllBy, | ||
getAllByTestId, | ||
getAllByName, | ||
getAllByType, | ||
getAllByText, | ||
|
@@ -15,6 +18,15 @@ import { | |
} from './getByAPI'; | ||
import { logDeprecationWarning, createQueryByError } from './errors'; | ||
|
||
export const queryBy = (instance: ReactTestInstance) => | ||
function queryByFn(criteria: Function | { [string]: any }) { | ||
try { | ||
return getBy(instance)(criteria); | ||
} catch (error) { | ||
return createQueryByError(error, queryByFn); | ||
} | ||
}; | ||
|
||
export const queryByName = (instance: ReactTestInstance) => | ||
function queryByNameFn(name: string | React.ComponentType<*>) { | ||
logDeprecationWarning('queryByName', 'getByName'); | ||
|
@@ -70,6 +82,16 @@ export const queryByTestId = (instance: ReactTestInstance) => | |
} | ||
}; | ||
|
||
export const queryAllBy = (instance: ReactTestInstance) => ( | ||
criteria: Function | { [string]: any } | ||
) => { | ||
try { | ||
return getAllBy(instance)(criteria); | ||
} catch (error) { | ||
return []; | ||
} | ||
}; | ||
|
||
export const queryAllByName = (instance: ReactTestInstance) => ( | ||
name: string | React.ComponentType<*> | ||
) => { | ||
|
@@ -121,13 +143,26 @@ export const queryAllByProps = (instance: ReactTestInstance) => (props: { | |
} | ||
}; | ||
|
||
export const queryAllByTestId = (instance: ReactTestInstance) => ( | ||
testID: string | ||
) => { | ||
try { | ||
return getAllByTestId(instance)(testID); | ||
} catch (error) { | ||
return []; | ||
} | ||
}; | ||
|
||
export const queryByAPI = (instance: ReactTestInstance) => ({ | ||
queryBy: queryBy(instance), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. please remove this as well (and all occurrences) |
||
queryAllByTestId: queryAllByTestId(instance), | ||
queryAllByName: queryAllByName(instance), | ||
queryAllByType: queryAllByType(instance), | ||
queryAllByText: queryAllByText(instance), | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 atestID
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 whichelement.type
is a string – this is the way we know the renderer rendered the "host node" (in case of RN, it's usuallyView
orText
).Something like this should be fine:
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:
Shows this:
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
andgetByAll
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. removegetByProps
helpers in v2).