How can I change the value of input in react test - reactjs

I'd like to test a form of my react-app.
I have removed 'disabled' property of button when doing following things.
const component = TestUtils.renderIntoDocument(<Mycomponent/>);
const myDOM =findDOMNode(component);
const input = myDOM.querySelector('input');
input.value = "2017-11-11";
let submitButton = myDOM.querySelector('button');
TestUtils.Simulate.click(submitButton);
...
const newlyAddedDate = record.querySelector('#date').innerHTML;
console.log("newlyAddedDate:"+newlyAddedDate);
but the output in console is
"newlyAddedDate:"
This react-app performs correctly in chrome.
I believe it's the problem of
"input.value="2017-11-11";
This sentence failed to change the value in the inputbox.
So how can i set the value in a inputbox when doing react-test?
Here's the repo of this app
https://github.com/zzbslayer/ChargeAccount-React

Well, it totally depends on what libraries you use for testing. Here's a sample using Mocha and Enzyme combination.
beforeEach(() => {
_spies = {}
_props = {
propOne: 'somepropvalue',
...
}
_wrapper = mount(<Your Component {..._props} />)
})
it('Should change the input box', () => {
let promptInput = _wrapper.find('textarea')
promptInput.simulate('change', { target: { value : 'sample2' } })
})
In before method you initialize your component, by passing all the props down. Then inside your test you find that text box inside the wrapper using a selector or HTML id of that element. Then you simulate a change event with the new value.
Notice that Jest has become a defacto framework for testing nowadays in most of the react starter boilerplate. In that case the concepts remains the same, but the syntax differs.

Related

Unable to find an element with the text: /7887/i. This could be because the text is broken up by multiple elements. you can provide a function

I am testing react app with react testing library and I came cross this error
Unable to find an element with the text: /9900/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
I have changed from toBeInTheDocument() to toEqual() but it didn't work too, and I tried to pass a function but it didn't work too?
const getByTextContent = (text) => {
return screen.getByText((content, element) => {
const hasText = element => element.textContent === text
const elementHasText = hasText(element)
const childrenDontHaveText = Array.from(element?.children || []).every(child => !hasText(child))
return elementHasText && childrenDontHaveText
})
}
Here is the test
expect(dataFetcher).toBeCalledTimes(1);
await waitFor(() => {
const playerId = screen.getByText(/9900/i);
expect(playerId.value).toEqual(9900);
expect(playerId).toBeInTheDocument(); // I tried this too but ogt the same result
});
I worked around this in my code logic by passing ? to safely access the object property, like this {candidateData && candidateData[i]?.name}, and not in the test file.
but I got this warning. I am wondering why this is happening
Warning: An update to Scorecard inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */

In Cypress component tests, changes to window object made inside component render cycle do not persevere

I need to expose some of my component's internals to Cypress component tests via window object, but I'm failing to pass and read it properly. Consider this code:
// component
import React, { FC } from 'react'
(window as any).publicDictionary = { }
export const getPublicDict = () => (window as any).publicDictionary
const addToPublicDict = (id: string) => (window as any).publicDictionary[id] = true
addToPublicDict('foo')
export const MyComponent: FC = () => {
addToPublicDict('bar')
return null
}
// test
it('Applies default value', () => {
mount(<MyComponent/>)
const dict = getPublicDict();
cy.log({
window,
dict,
foo: dict.foo,
bar: dict.bar,
dictKeys: Object.keys(dict),
} as any)
})
This code behaves very weirdly when inspected via Chrome devtools:
... when logging publicDict directly, both foo and bar properties seem to be present - but when I try to access them, bar turns out to be undefined, because it was set inside the component's render cycle. foo, on the other hand, can be read and accessed as expected.
What is going on? What can I do to successfully pass my component's internals to Cypress in component tests?
You're exporting export const getPublicDict... so import it like this
import {getPublicDict} from '../src/my-component'
With this import, the properties show up.
Note, the window object isn't the same in test and app. In component testing they mount the component on the test runner window.
I haven't found the cause, but I found the solution.
window object only works properly for me, when I access it while chained after cy.window command:
import { getPublicDict } from './utils'
// This works:
it('works', () => {
cy.window().then(() => {
// Here, window and its properties work as expected
const dict = getPublicDict();
})
})
// This doesn't:
it('works', () => {
// Here, window tends to get quite quirky
const dict = getPublicDict();
})
This way it works well - I don't even have to use the window object yielded by cy.window - it's sufficient I access window while chained after cy.window.
If anyone knows why this is, I'm curious.

react jest testing - how to test upload file when the file input is not part of the component but created in the document using a hook

I have a hook called useFileInput which uses input ref and creates an input element.
export const useFileInput = (onSelectFiles) => {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current = document.createElement('input');
inputRef.current.type = 'file';
}, []);
...
it returns a callback that triggers a click on the input which opens a native file selector.
I want to test the component that uses this hook by uploading a file and doing some stuff.
If the input was os the component itself I would do something like this:
await waitFor(() => userEvent.upload(screen.getByTestId('upload-file-input'), file));
but now, the input is not on the screen so I can't do it.
how to achieve this? or maybe it means that the hook is not weel written?

How do I re-render a component in Jest?

I have a beforeEach() that renders my app:
describe('...', () => {
beforeEach(() => {
render(<App />);
});
...
});
I am currently going through a config change in my app. config was previously a boolean value in localStorage and now it has a more appropriate value like prod and dev. My app checks the old boolean values when it first loads and it changes them to their respective non-boolean value - here's the mapping:
true -> prod
false -> dev
I am trying to write tests for this transformation in Jest. The idea behind the first test is to set the localStorage item, re-render the app, and check the the localStorage item has been updated correctly:
it('should show dev config when using old boolean value', () => {
const toggle = screen.getByTestId('toggle');
expect(toggle).toBeChecked();
localStorage.setItem('config', false);
// re-render app and check localStorage
render(<App />);
const toggle2 = screen.getByTestId('toggle');
expect(toggle2).not.toBeChecked();
expect(localStorage.getItem('config')).toEqual('dev');
});
However, this test throws an error presumably because the app is now rendered twice instead of once:
TestingLibraryElementError: Found multiple elements by: [data-id="toggle"]
How do I re-render the app so that it isn't duplicated in the test environment?
If you are using react-testing-library you can use the reredner method that is returned from the initial render. You can use this method to call render again and provide the same container
that your first call created (you can even pass new props).
Docs: https://testing-library.com/docs/example-update-props/
Example:
it('should add the fixed width class if position is fixed', () => {
const {rerender} = render(<PageNavigation position="fixed-left" fixedWidth="test-123" />);
const list = screen.getByTestId('page-navigation'); // Or however you're selecting
expect(list).toHaveClass('test-123');
rerender(<PageNavigation position="fixed-right" fixedWidth="test-456" />);
// Sanity check to ensure the first class was removed
expect(list).not.toHaveClass('test-123');
expect(list).toHaveClass('test-456');
});
You could use the rerender function from react-testing-library : https://testing-library.com/docs/marko-testing-library/api/#rerender
In your test :
import { ..., rerender } from '#testing-library/react';
it('should show dev config when using old boolean value', () => {
const toggle = screen.getByTestId('toggle');
expect(toggle).toBeChecked();
localStorage.setItem('config', false);
// re-render app and check localStorage
rerender(<App />);
const toggle2 = screen.getByTestId('toggle');
expect(toggle2).not.toBeChecked();
expect(localStorage.getItem('config')).toEqual('dev');
});

enzyme testing react with context api

I'm trying to test a form. When the form is submitted it should set a state on error: true and then a div with an error information should appear. My test looks like this:
outer = shallow(<Search />);
it("should not submit a form and display an error if title or author is empty", () => {
const Children = outer.props().children({});
const wrapper = mount(Children);
const button = wrapper.find("form button");
button.simulate("submit", {
preventDefault() {
outer.setState({ error: true });
}
});
expect(wrapper.find("div.error")).toHaveLength(1);
});
Unfortunately it doesn't work. I am new to unit testing and I have no idea if I'm doing it correctly and how should I fix that.
I think I also should get somehow inputs values but don't know how.
This is the sample to set value to input elements:
it('Should capture firstname correctly onChange', function(){
const component = mount(<Form />);
const input = component.find('input').at(0);
input.instance().value = 'hello';
input.simulate('change');
expect(component.state().firstname).toEqual('hello');
})
But it may not work because of different other reasons, make sure you have initialized enzyme components in beforeAll(). Try to read enzyme's examples about this topic.

Resources