Can I call a method without using .simulate() - Jest Enzyme - javascript

I'm unit testing for a React flight seat selecting app using Jest/Enzyme. Is there a way I can test a method within my class based component which would run after a button is clicked, but without actually simulating the button click? This is because the button is within a child of a child component and the method is being passed down as a prop.
Albeit a very simple function, I'd still like to test it
inputSeats(chosenSeats) {
this.setState({
chosenSeats: chosenSeats
})
}
This is in a parent component called FlightSeats, with a child of SeatMaps, and SeatMaps has 2 children of SeatMap (inbound/outbound).
Within each of these SeatMap components there is the 'Reserve Seats' button which when clicked it performs some validation tests, calls another method in SeatMap and eventually calls inputSeats() from within SeatMaps component. I'm struggling to simulate the button click since it is deep within the app.
Ideally, in my unit test, I'd just to like to call it with something like
FlightSeats.inputSeats(chosenSeats)
and pass in my mock data as chosenSeats... Or would I have to import the child components and mount them and use .simulate('click') on the button?
My test so far:
let chosenSeats = {
outbound: [{
seatNo: "11A",
seatPrice: "11.11",
}, {
seatNo: "12A",
seatPrice: "12.12"
}],
inbound: [{
seatNo: "14A",
seatPrice: "14.14",
}, {
seatNo: "15A",
seatPrice: "15.15"
}]
};
let wrapper, buildEmptySeats, clearSeats, comp;
beforeEach(() => {
comp = ( < FlightSeats seats = {
seatsArr
}
party = {
partyArr
}
flights = {
flightArr
}
/>);
wrapper = shallow(comp); component = mount(comp);
});
test('should handle inputSeats correctly', () => {
// what to call here??
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
});

I assume you are just testing the functionality, then it should be fine to directly call the method in parent component but its usually good to test the button clicked simulation process as this what the user clicks.
Here is a simple example based on your code above
const chosenSeats = {...};
const component = mount(comp);
test('should handle inputSeats correctly', () =>{
//here get the instance of the component before you can call its method
component.instance().inputSeats(chosenSeats);
//here you call state like you've already done
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
};

Related

Vitest -Unable to access the DOM inside a test

I am new to front-end testing, and I am trying to get a good grasp on it.
In the process, inside one project, I am creating a test for a Vue component. I want to test that the behaviour of the code is correct (The component has code inside the mounted() hook that has to perform basically some checks and an API call).
I want to check that the code reaches one method. Previously to that, the code creates a click event listener to one element in the DOM.
My test emulates a click event (triggers it), but it cannot assert that the proper method has been called after the click event.
This is due to it not finding the element in the DOM to which it has to add the event listener. It seems that the code cannot find anything inside the document (using .getElementById()).
I wonder why, and how I would resolve this, since I have been stuck here for hours and I haven't found any solution that could work here, even when I have learned some interesting things in the process. I will leave a code example with the code structure I have built:
Inside the component:
<template>
// ...
<button id = "myButton">Add</button>
</template>
<script>
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile ";
let classIESF = new classInExternalScriptsFile();
export default {
methods: {
setup: function () {
classIESF.setupMethod();
},
},
mounted() {
this.setupMethod();
},
};
</script>
Inside the scriptsFile
export class classInExternalScriptsFile {
setupMethod() {
let myButton = document.getElementById("myButton") // <-- getElementById() returns a null here
if (typeof myButton !== "undefined" && myButton !== null) {
myButton.onclick = () => { // <-- The test code complains because it cannot enter here
// Some lines...
this.mySuperMethod()
}
}
}
mySuperMethod() {
// API call etc.
}
}
Inside the .spec.js test file:
// imports...
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile.js";
describe("description...", () => {
const mySuperMethodMock = vi
.spyOn(classInExternalScriptsFile.prototype, "mySuperMethod")
.mockImplementation(() => {});
test("That the button performs x when clicked", () => {
let wrapper = mount(myComponent, {
props: ...,
});
let myButton = wrapper.find('[test-id="my-button"]');
myButton.trigger("click");
expect(mySuperMethodMock).toHaveBeenCalled(); // <-- The test fails here
}
}

Can i setState of a react functional component from a external gloabal function present inside script tag?

I have an android app which calls the a function present inside the head of my react
page. All i want is to allow the the function to set a state inside the react component
<head>
<script>
webViewAndriodInteraction(parm1)
{
//Here i want this function to change the state of my react functional component
//setSomeState(pram)
}
</script>
</head>;
function LiveScreen(props) {
const [somedata, setSomeState] = useState();
return <h1>someData</h1>;
}
It's probably against some React best practices, but you could do this:
In your component, define an effect that puts your state setter on the window object:
useEffect(() => {
window.setSomeState = setSomeState;
return () => {
window.setSomeState = undefined;
};
}, []);
And in your webViewAndriodInteraction function:
if (window.setSomeState !== undefined) {
// call window.setSomeState here
}
You also need to ensure that your call to window.setSomeState is deferred until the function gets defined. So if you're sure it's going to get defined, you could set a timeout or retry the if check a few times with a given delay.

Get child props in Jest? (NO ENZYME)

I'm trying to test whether a React button component's text changes on-click. This is roughly what gets returned inside the component I'm testing (let's call it RandComponent):
return (<>
...
<ButtonComponent
data-testid='testButton'
>
{getButtonText()}
</ButtonComponent>
</>)
I want to be able to access whatever getButtonText() returns in Jest. I've looked online and in enzyme you can do something like this in the test file:
it('test button text toggle', async ()=>{
let container;
await act(async ()=> container = render(<RandComponent/>));
const button = getByTestId('testButton');
expect(button.text()).toEqual(whatever getButtonText() is supposed to return);
}
My problem is that I can't use enzyme... is there a way to get the child prop text like in the last line (button.text()) in Jest?
This requires to stub ButtonComponent by means of Jest and assert props that were provided to it:
jest.mock('...ButtonComponent module...', () => jest.fn());
...
let buttonProps;
ButtonComponent.mockImplementation(props => {
buttonProps = props;
return <div>ButtonComponent</div>
});
container = render(<RandComponent/>));
...
expect(buttonProps.children).toBe(...);
This is boilerplate code that Enzyme is supposed to save from.

React-jest-enzyme: testing callback of child component that calls another function first before calling the callback

I am testing wether a callback that is passed to a child component is called after my button in the child component is clicked. I simulate the react-bootstrap button, <Button></Button>, by using the .simulate('click') function.
The problem is that the onClick() function of my button calls another function called update() and that function calls the handleSave callback passed to my child component. The onKeyPress function of the <FormControl/> element also calls the update function of my component. Here is how I have my child component setup:
update(event) {
//Have to check to see if the key stroke is the enter key or if it comes from the button click.
if(event.charCode === 13 || event.type === 'react-click'){
// Have to use this get the ref value because this.refs.input.value doesn't work.
var input = ReactDOM.findDOMNode(this.refs.input);
input.value = '';
this.props.handleSave();
}
}
render(){
return(
<Form>
<FormControl type="text" ref="input" onKeyPress={this.update.bind(this)} placeholder="Enter Note" />
<Button onClick={this.update.bind(this)}>Submit </Button>
</Form>
)
}
That is why my update() function has a check to see if came from charCode==13, that is the charCode for the enter key, or the button click because both save the info that is in the <FormControl />
I have my test setup this way:
describe('Input', () => {
const mockHandleText = jest.fn();
const mockHandleSave = jest.fn();
const props = {handleSave: mockHandleSave}
let input = shallow(<Input {...props} />);
describe('when entering a note', () => {
beforeEach(() => {
input.find('Button').simulate('click', {
charCode: 13
});
});
it('adds the note to state', () => {
expect(props.handleSave).toHaveBeenCalled();
});
});
});
A weird thing is that I have to pass an object as a second parameter to the .simulate() function because if I don't it will give me an error saying cannot read charCode of undefined but when a pass an object, the object doesn't even have to have an event property, then it just says
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
Also If I don't pass in the object with some property then it also breaks my other test that I have for a callback on the onChange function of my element. I left it out of the code sample for the sake of simplicity and just uploaded the code that is giving me problems. I am also using a bootstrap form with and . The full code is on my github at github.com/Alebron23.
Enzyme's shallow method doesn't render the whole DOM tree, just the most shallow level. You'll not be able to find nested children using it. In the docs for shallow (https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md), they discuss that if you need to assert any behavior on child components, you'll have to use something other than shallow().
Your other options are to either use render(), or more likely- since render() is static and you want to test side effects- to fully mount()
the component (https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md) instead.

Rendering an array of html elements

I want to render an array of html elements in my component. The reason for storing the data/html in an array is because I want to be able to dynamically load a new element depending on a button-click.
This is how I want to display my array:
<div>
{this.state.steps}
</div>
This is how I initiate my component and array:
componentDidMount() {
this.createProcessStep().then(step => {
this.setState({steps: this.state.steps.concat(step)});
});
}
export function createProcessStep() {
this.setState({processStepCounter: this.state.processStepCounter += 1});
return this.addStepToArray().then(d => {
return this.reallyCreateProcessStep()
});
}
addStepToArray = () => {
const step = {
...Some variables...
};
return new Promise(resolve => {
this.setState({
stepsData: this.state.stepsData.concat(step)
}, resolve)
});
};
"stepsData" is another array that holds data (variables) belonging to each step. "steps" on the other hand, should only hold the html.
This is how one step/element looks like:
<div>
...Some Content...
<button label="+" onClick={ () => {
this.createProcessStep().then(step => {
this.setState({
steps: this.state.steps.concat(step)
});
})
}}/>
...other content...
</div>
This button within each step is responsible for loading/adding yet another step to the array, which actually works. My component displays each step properly, however react doesn't properly render changes to the element/step, which is
to say that, whenever e.g. I change a value of an input field, react doesn't render those changes. So I can actually click on the "+"-button that will render the new html element but whenever a change to this element occurs,
react simply ignores the phenotype of said change. Keeping in mind that the changeHandlers for those steps/elements still work. I can change inputfields, radioButtons, checkboxes etc. which will do exactly what it's
supposed to, however the "re-rendering" (or whatever it is) doesn't work.
Any ideas of what I'm doing wrong here? Thanks!
While you could certainly beat your approach into working, I would advise that you take more common react approach.
You make your components to correctly display themselves from the state . ie as many steps are in the state, your component will display. Than make your add button add necessary information (information, not formated html) to the state.
Here is an example how to use component N times:
const MyRepeatedlyOccuringComponent = (n) => (<p key={n}>There goes Camel {n}</p>)
const App = () => {
const camels = [1,22,333,4444,55555]
const caravan = camels.map((n) => MyRepeatedlyOccuringComponent(n))
return(<div>{caravan}</div>
}

Categories