Alternative to using `getByTestId()` in React-testing-library - create-react-app

I am currently using getByTestId() for a test in my React app. The test looks like this:
it('should be truthy when clicking to play or pause if "hasVideo" is true', () => {
const newProps = {
hasVideo: true
}
render(<VideoCardForm {...newProps} />);
const videoEl = screen.getByTestId('video-play-pause');
const result = fireEvent.click(videoEl);
expect(result).toBeTruthy();
});
And the relevant DOM section looks like this:
<video
data-testid="video-play-pause"
className={this.state.isPlaying ? classes.VideoOnPlay : classes.VideoPrep }
onClick={(event) => {
this.onVideoClick(this.props.url, event);
}}
src={`${this.props.url}`}
>
</video>
This works. But it seems hacky to me to add something to the DOM like this: data-testid, solely for the purpose of running a test. Is there an alternative I could add to the DOM, that actually serves a semantic purpose beyond just allowing for the running of a test?

You can get the DOM through render itself in container property.
const { container } = render(...).
If video is the nested DOM you can always use container.querySelector('video') to get the video DOM.

Related

ReactNative UI freezing for a second before rendering a component with a fetch in useEffect()

TL;DR: My UI freezes for .5-1s when I try to render a component that does a API fetch within a useEffect().
I have ComponentX which is a component that fetches data from an API in a useEffect() via a redux dispatch. I'm using RTK to build my redux store.
function ComponentX() {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchListData()); // fetch list data is a redux thunk.
}, [dispatch]);
...
return <FlatList data={data} /> // pseudo code
}
as you can see the fetch will happen everytime the component is rendered.
Now I have ComponentX in App along with another component called ComponentY.
Here's a rudamentary implementation on how my app determines which component to show. Pretend each component has a button that executes the onClick
function App() {
const [componentToRender, setComponentToRender] = useState("x");
if (componentToRender === "x") {
return <ComponentX onClick={() => setComponentToRender("y")}/>
} else {
return <ComponentY onClick={() => setComponentToRender("x")}/>
}
}
Now the issue happens when I try to move from ComponentY to ComponentX. When I click the "back" button on ComponentY the UI will freeze for .5-1s then show ComponentX. Removing the dispatch(fetchListData()); from the useEffect fixes the issue but obviously I can't do that since I need the data from the API.
Another fascinating thing is that I tried wrapping the dispatch in an if statement assuming that it would prevent a data fetch thus resolving the "lag" when shouldReload is false. The UI still froze before rendering ComponentX.
useEffect(() => {
if (shouldReload) { // assume this is false
console.log("reloading");
dispatch(fetchListData());
}
}, [dispatch, shouldReload]);
Any idea what's going on here?
EDIT:
I've done a little more pruning of code trying to simplify things. What I found that removing redux from the equation fixes the issue. By simply doing below, the lag disappears. This leads me to believe it has something to do with Redux/RTK.
const [listData, setListData] = useState([]);
useEffect(() => {
getListData().then(setListData)
}, []);
Sometimes running the code after interactions/animations completed solves the issue.
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
dispatch(fetchListData());
});
}, [dispatch]);

Trigger Topbar buttons in test

I'm trying to test navigation events on a screen using react-native-testing-library.
I'm listening to the events globally using Navigation.events().registerNavigationButtonPressedListener with the following hook inside my Functional component:
const useTopBarBtnPress = function (
componentId: string,
onTopBtnPressed: OnTopBtnPressed) {
useEffect(() => {
const topBtnListener = Navigation.events().registerNavigationButtonPressedListener((event) => {
if (event.componentId === componentId)
onTopBtnPressed(event, BtnIds)
})
return () => topBtnListener.remove()
}, [onTopBtnPressed])
}
Is it possible to simulate a topBar button for the test ? I guess using the testID but I can't find it in the doc.
Or do I need to mock registerNavigationButtonPressedListener ? Or use Detox ?
Also, is there a way to test the layout ? (eg. Icon color)
There's no need to actually mock TopBar buttons to test navigationButtonPressed. Simply invoke navigationButtonPressed yourself with the correct NavigationButtonPressedEvent parameter simulating a button press.

Check if navigation.navigate is called in react native testing

I've been using 'react-test-renderer' for testing react-native, Mostly on it's functions. Lately I've found out that there is a testing library for react-native which is #testing-library/react-native. What I wanted to do is to check if navigation.navigate or alert has been called in a function. I don't know which of these libraries can achieve that and how.
onSubmitPress = () => {
if (true) {
this.props.navigation.navigate('MainPage');
else {
showAlert( "Not Allowed);
}
You can use 'react-test-renderer':
like this:
const fakeNavigation = {
navigate: jest.fn(),
};
const tree = renderer.create(
<YourComponent navigation={fakeNavigation} />
);
after that you can use jest expect function to test if mock navigation.navigate is called someThing like this:
const instance = tree.getInstance();
instance.onSubmit();
expect(fakeNavigation.navigate).toBeCalledWith('MainPage')
for react-native-testing-library it's a bit different as you test mostly what user sees depending of the behavior of your component.
Basically you look for your button that calls onSubmit and then use fireEvent from react-native-testing-library to press it, after that you expect the text of your alert to be shown in screen or not.
If you have a button like this in your component=
<TouchableOpacity onPress={onSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
You can test the behavior of you function like this:
const {getByText} = render(<YourComponent/>)
let buttonText = getByText('Submit');
fireEvent(buttonText.parent, 'press'); //parent to get to TouchableOpacity
alertText = getByText('Not Allowed');
expect(alertText).toBeDefined();

Initializing a map in firestore

I'm trying to build an app using react native with a firestore database. I'm fairly new to the react native framework (as well as working with firestore), so it's possible I might be trying to solve this problem the wrong way.
I have a database that works well and is already populated. For each user of this app, I'd like to add a map to their entry. I want to use this map to store some data about the user which they can fill out later.
Here's some code:
componentDidMount() {
this.readProfile(this.props.uid);
}
readProfile = (uid) => {
this.props.getProfile(uid).then((profile) =>
{
if(!profile.userMap)
{
profile.userMap = generateUserMap();
}
...
}
export const generateUserMap = function () {
var map = new Map();
SomeEnum.forEach((key, value) => {
map.set(key, false);
});
AnotherEnum.forEach((key, value) => {
map.set(key, false);
});
OneMoreEnum.forEach((key, value) => {
map.set(key, false);
});
return map;
};
...
<Input
value={this.state.profile.userMap[SomeEnum.Foo]}
onChangeText={(foo) => this.updateUserMap({ foo })}
/>
What I want this code to be doing is to read in the user's profile when I load the page. That part seems to be working fine. My next concern is to properly initialize the map object. The code doesn't seem to be properly initializing the map, but I'm not sure why. Here's why I say that:
TypeError: Cannot read property 'Foo' of undefined
With the stack trace pointing to my component's Connect() method.
Any help would be greatly appreciated.
EDIT: Apologies for the oversight, here is the updateUserMap function:
updateUserMap = (property) => {
const profile = Object.assign({}, this.state.profile, property);
this.setState({ profile });
}
So, as anyone who looks over this question can probably tell, I was doing a few things pretty wrong.
The error I'm getting referred specifically to that input block in my render method - this.state.profile.userMap was undefined at that point. I can guarantee that it won't be undefined if I do my check within the render method but before I'm accessing the userMap. Because of how the lifecycle methods work in react native, ComponentDidMount wouldn't be called before my render method would.
My enum code also wouldn't work. I changed that to a simple for loop and it works like a charm.
Here's my updated code:
render() {
if(!this.state.profile.userMap)
{
this.state.profile.userMap = generateUserMap();
}

Spying on React components using Enzyme (and sinon?) to check arguments

I'm wanting to assert that a component gets called from within another component with the correct arguments.
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
I'm pretty sure I want to use a sinon spy and do something like this
const titleSpy = sinon.spy(Title, render)
expect(titleSpy).to.be.calledWith( '< some title >' )
but with regards to React and Enzyme, I'm not really sure what I should be spying on. (Because apparently it's not render!)
In my spec file I am importing Title and console.loging it's value to find a function to spy on and I get:
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, Object.getPrototypeOf(_class).apply(this, arguments));
}
Any ideas on how I can do this? Is it a case of going through and finding the element and checking it's attributes? If so that seems a bit...messy and seems like it goes against the principle of the Shallow render ("Shallow rendering is useful to constrain yourself to testing a component as a unit").
If you're just checking the value of properties passed to the component, you don't need sinon. For example, given the following component:
export default class MyComponent extends React.Component {
render() {
return (
<MyComponent myProp={this.props.myProp} />)
}
}
Your test might look like this:
describe('MyComponent ->', () => {
const props = {
myProp: 'myProp'
}
it('should set myProp from props', () => {
const component = shallow(<MyComponent {...props} />)
expect(component.props().myProp).to.equal(props.myProp)
})
})
You can achieve it with the help of .contains() method, without messing up with spies.
If you have a component:
<Foo>
<Title title="A title" url="http://google.com" />
</Foo>
You can make such an assertion:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A title" url="http://google.com" />)).to.equal(true);
Such will fail:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A wrong title" url="http://youtube.com" />)).to.equal(true);
This is an older question, but my approach is a little different than the existing answers:
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
ie. You're wanting to check that the component being tested renders another component, and passes the correct prop(s) to it.
So if the component being tested looks something like:
const MyComp = ({ title, url }) => (
<div>
<Title title={title} url={url} />
</div>
)
Then the test could look something like:
import Title from 'path/to/Title';, u
it('renders Title correctly', () => {
const testTitle = 'Test title';
const testUrl = 'http://example.com';
const sut = enzyme.shallow(<MyComp title={testTitle} url={testUrl} />);
// Check tested component rendered
expect(sut.exists).toBeTruthy();
// Find the Title component in the subtree
const titleComp = sut.find(Title); // or use a css-style selector string instead of the Title import
// Check that we found exactly one Title component
expect(titleComp).toHaveLength(1);
// Check that the props that were passed were our test values
expect(titleComp.prop('title')).toBe(testTitle);
expect(titleComp.prop('url')).toBe(testUrl);
});
I generally find Enzyme's functions to be very useful for all kinds of checks about components, without needing other libraries. Creating Sinon mocks can be useful to pass as props to components, to (for example) test that a callback prop is called when a button is clicked.