Stenciljs having troubles testing emitted event with Jest - jestjs

I'm writing an input component using stencil and I'm having troubles testing the emitted event which is fired on a click or enter key press.The component:
export class Input {
.
.
private inputRef?: HTMLInputElement;
#Event() inputEvent: EventEmitter<{}>;
.
.
render() {
return (
<div class="status">
<div class="input-wrapper">
<input onKeyUp = {event => this.handleKeyDown(event)} ref={el => this.inputRef = el as HTMLInputElement}/>
</div>
</div>);
}
private handleKeyDown(e: KeyboardEvent): void {
this.dispatchSearchEvent(e);
}
private dispatchSearchEvent(e){
this.inputEvent.emit({type: "event-name", event: e, data: (this.inputRef as HTMLInputElement).value});
}
}
conponent.spec.jsx:
it('fires an event upon enter key press', async () => {
const component = new InputFields();
jest.spyOn(component, 'event-name');
const keyUpEvent = new Event("keyup", {
key: "enter",
keyCode: 13,
which: 13,
shiftKey: false,
ctrlKey: false,
metaKey: false
});
keyUpEvent.target = {value: "some input value"}
component.handleKeyDown(keyUpEvent);
component.inputEvent = { emit: jest.fn().mockReturnValue(Promise.resolve()), };
expect(component.inputEvent.emit).toHaveBeenCalled();
});
Having an error:
Cannot spy the event-name property because it is not a function; undefined given instead
356 | it('fires an event upon enter key press', async () => {
357 | const component = new Input();
> 358 | jest.spyOn(component, 'event-name');
| ^
359 |
360 | const keyUpEvent = new Event("keyup", {
361 | key: "enter",

The error message is trying to tell you that your Input component does not have a function named event-name. spyOn is for spying on functions not events.
In E2E testing you can use E2EElement.spyOnEvent for events:
const page = await newE2EPage({html: '<my-input></my-input>'});
const component = await page.find('my-input');
const spy = await component.spyOnEvent('inputEvent');
await component.type('x');
expect(spy).toHaveReceivedEventTimes(1);
expect(spy.firstEvent.detail.data).toBe('x');

Related

Getting and Inserting array of objects from checkboxes ReactJS

I am having a trouble where an array of Objects are returning [Object object]. What could be the missing fix to get the value of product from the mapped targeted values.
this is my sample array.
const product = [{food:'BREAD',price: 6}]
this is where I map the values and get the targeted value.
<Form >
{product.map((item, index) => (
<div key={index} className="mb-3">
<Form.Check
input value={[item]}
id={[item.food]}
type="checkbox"
label={`${item.food}`}
onClick={handleChangeCheckbox('PRODUCTS')}
/>
</div>
))}
</Form>
this handles the e.target.value from checked checkboxes.
const handleChangeCheckbox = input => event => {
var value = event.target.value;
var isChecked = event.target.checked;
setChecked(current =>
current.map(obj => {
if (obj.option === input) {
if(isChecked){
return {...obj, chosen: [...obj.chosen, value ] };
}else{
var newArr = obj.chosen;
var index = newArr.indexOf(event.target.value);
newArr.splice(index, 1); // 2nd parameter means remove one item only
return {...obj, chosen: newArr};
}
}
return obj;
}),
);
console.log(checked);
}
finally, this is where I am having problems. Chosen is returning [Object object]console.log(checked).
const [checked, setChecked] = useState([
{ option: 'PRODUCTS',
chosen: [],
}
]);
What do I insert inside chosen:[] to read the following arrays. Im expecting to see
0:
food: 'bread'
price: '6'
Thank you so much for helping me!
Html input value prop is a string, and it's change event target value is also string.
Here you are passing an object to the value prop, which will be stringified as [Object object].
Instead, update your change handler to take item instead of event.
const handleChangeCheckbox = (input) => (value) => {
setChecked((current) => {
// Value is checked if it exists in the current chosen array
const isChecked = current.chosen.find((item) => item.food === value.food) !== undefined;
// Remove it from state
if (isChecked) {
return {
...current,
chosen: current.chosen.filter((item) => item.food === value.food),
};
}
// Add it to state
return {
...current,
chosen: [...current, value],
};
});
};
Then update your input element onChange handler, to call your handler with the item itself, instead of the event.
onClick={() => handleChangeCheckbox('PRODUCTS', item)}
I don't know what the props for your component Form.Check are. But, I would expect an input type="checkbox" to have a checked prop.
A checkbox is checked if the item is in the chosen state array.
<Form>
{product.map((item, index) => (
<div key={item.food} className="mb-3">
<Form.Check
type="checkbox"
id={item.food}
label={item.food}
checked={checked.chosen.find((chosen) => chosen.food === item.food) !== undefined}
onClick={() => handleChangeCheckbox('PRODUCTS', item)}
/>
</div>
))}
</Form>
Hmm, don't you need to inline your handleChangeCheckbox function? As otherwise it's just getting executed. So instead onClick={handleChangeCheckbox('PRODUCTS')} do onClick={(event) => handleChangeCheckbox('PRODUCTS', event)}.
Then your handleChangeCheckbox function will start handleChangeCheckbox = (input, event) => {...}

TypeError: navigation.navigate is not a function in jest

Here is touchable element with navigation.navigate (react navigation 6.x),
export default Home = ({ navigation }) => {
....
return (
<TouchableOpacity testID={"home.myhelp"} onPress={()=>{navigation.navigate("MyHelp")}} >
<View style={{flex:2, alignContent:"flex-start", justifyContent:"center",alignItems:"center"}}>
<Icon name="help-outline" color="black" size={hp("4.4%")}/>
</View>
</TouchableOpacity>
)
Here is the jest code which uses fireEvent to simulate the click of the touchable element:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render, cleanup, screen, fireEvent, waitFor } from "#testing-library/react-native";
import { authContext, stateContext, propsContext } from '../app/GlobalContext';
import Home from './Home';
describe ('Home page view', () => {
test ('home page link', async () => {
jest.mock('react-native-easy-grid', () => jest.fn());
//jest.mock('react-native-device-info', () => () => jest.fn());
const navigation = jest.fn();
const propcontext = {device_id:"a device id", result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}};
const contextval = jest.fn().mockImplementation(() => {
let temp = {}
});
const propsVal = {device_id:"a device id"};
const authVal = {result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}};
const stateVal = {name:"me", alias:"akkus", aka:"aka"};
//const spy = jest.spyOn(jest.fn(), "submit");
const submit = jest.fn();
//= jest.fn().mockImplementation();
const component = (<NavigationContainer>
<propsContext.Provider value={propsVal}>
<authContext.Provider value={authVal}>
<stateContext.Provider value={stateVal}>
<Home navigation={navigation} />
</stateContext.Provider>
</authContext.Provider>
</propsContext.Provider>
</NavigationContainer>);
const wrapper = render(component);
expect(screen.getByTestId('home.myhelp')).toBeTruthy();
fireEvent.press(screen.getByTestId('home.myhelp')); //this line cause the error
})
})
However the fireEvent causes error about navigation.navigate:
TypeError: navigation.navigate is not a function
121 | <TextInput style={{fontSize:hp("3%")}} placeholder={plcholder} onChangeText={strChg}></TextInput>
122 | </View>
> 123 | <TouchableOpacity testID={"home.myhelp"} onPress={()=>{navigation.navigate("MyHelp")}} >
| ^
124 | <View style={{flex:2, alignContent:"flex-start", justifyContent:"center",alignItems:"center"}}>
125 | <Icon name="help-outline" color="black" size={hp("4.4%")}/>
126 | </View>
at navigate (src/components/home/Home.js:123:83)
at handler (node_modules/#testing-library/react-native/src/fireEvent.ts:124:19)
at act (node_modules/react/cjs/react.development.js:2510:16)
at invokeEvent (node_modules/#testing-library/react-native/src/fireEvent.ts:123:3)
at Function.invokeEvent [as press] (node_modules/#testing-library/react-native/src/fireEvent.ts:134:3)
at Object.press (src/components/home/Home.test.js:56:23)
at asyncGeneratorStep (node_modules/#babel/runtime/helpers/asyncToGenerator.js:3:24)
at _next (node_modules/#babel/runtime/helpers/asyncToGenerator.js:25:9)
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
at flush (node_modules/asap/raw.js:50:29)
How to mock navigation.navigate to calm the error?
Solved the error after defining navigation as below:
const navigation = {navigate:jest.fn()};
or
const navigation = {navigate:()=>jest.fn()};

Shallow component not updating on jest.fn call

After updating a series of dependencies, most notably jest and react/react-dom, a once working Unit Test is no longer working. After spending the last week reading through the ChangeLogs of the dependencies that changed, I still cannot find out why it is breaking.
The Component - stripped down for the relevant portions
[imports, etc.] ->not code, just giving a stripped down version
class MyComponent extends React.Component {
render() {
const { Foo, errorNotice, disabled } = this.props;
return (
<form autoComplete="Off">
<Paper className="top-paper edit-form">
<h1>{ Foo.id ? 'Edit' : 'Add' } My Foo </h1>
<div className="flex">
<div className="flex-column">
<FormControl
className="has-columns"
component="fieldset"
>
<TextField
id="foo-name"
fullWidth={true}
disabled={disabled}
name="name"
inputProps={{ maxLength: 50 }}
className="block"
label="Name"
value={Foo.name}
onChange={this.props.onChange}
error={!!errorText.name}
helperText={errorText.name}
/>
[closing tags, etc.] -> as as above, not code, just giving a stripped down version
export default MyComponent
The Test
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from "./my-component";
const Foo = {
name: 'Foo Name',
val_x: 'NONE'
};
const handleTextChange = jest.fn(({ target: { name, value} }) => {
Foo[name] = value;
testMyComponent.setProps({ Foo });
});
const testMyComponent = shallow(
<MyComponent
Foo={Foo}
errorNotice={{}}
onChange={handleTextChange}
/>
);
describe('Test component display', () => {
it('Name field show display attachment point name', () => {
const nameInput = testMyComponent.find('[name="name"]');
expect(nameInput.props().value).toBe(Foo.name);
});
});
^^ This Test Passes
describe('Test Foo interactions', () => {
it('Updating Name field should update Foo name', () => {
const newName= 'New Name';
testMyComponent.find('[name="name"]').simulate('change', {
target: { name: "name", value: newName }
});
expect(testMyComponent.find('[name="name"]').props().value).toBe(newName);
});
});
^^ This Test Fails on the 'expect' line. The name remains the old name, 'Foo Name'
The output when I call testMyComponent.debug() after the .simulate('change' is as follows (again stripped down)
<WithStyles(ForwardRef(TextField)) id="foo-name" fullWidth={true} disabled={[undefined]} name="name" inputProps={{...}} className="block" label="Name" value="Foo Name" onChange={[Function: mockConstructor] { _isMockFunction: true, ... , results: [ Object [Object: null prototype] {type: 'return', value: undefined } ], lastCall: [ { target: { name: 'name', value: 'New Name' ....
^^ So I can see through lastCall that the handleTextChange function is being called, but its not actually performing the update. Moreover, if I put a test for
expect(handleTextChange).toHaveBeenCalledTimes(1);
Then that text passes, it effectively gets called. But again, the update doesn't actually occur.
The dependencies that were changed were
react 16.13.1 -> 17.0.2
react-dom 16.13.1 -> 17.0.2
jest 24.9.0 -> 27.5.1
material-ui/core 4.11.0 -> 4.12.13
but enzyme stayed the same a 3.11.0
Does any of this make any sense? Like I mentioned I've read changelogs and update posts on all of the dependencies that were updated and I cant see anything that needs to change in the test, but clearly it is failing. I have read Jest/Enzyme Shallow testing RFC - not firing jest.fn() but the solution there is not working for me. I should also mention I have called .update() on the component but to no avail.

How to make states from the keys of state object in react?

So I was trying to implement states in Child component from the Parent component state,as you can see in the code.But it gives me undefined as state value in child componenet.To test you can conosle.log(questions) and you will see undefined.
Is there a mechanism to setState in Parent component in some way such that the subsequent props in child components wil be able to get the state values?
Here is my code:
import React, { useEffect, useState } from "react";
import io from "socket.io-client";
const ENDPOINT = "http://localhost:5000";
let socket = io(ENDPOINT);
export default function Screen() {
const [qValue, setQuestion] = useState({personalInfo:{},questions:[]});
const [aValue, setAnswer] = useState({personalInfo:{},answer:""});
useEffect(() => {
socket.on("screenAns", (input) => {
setAnswer(JSON.parse(input));
});
console.log(aValue);
}, [aValue]);
useEffect(() => {
socket.on("screenQs", (arrayValue) => {
setQuestion(JSON.parse(arrayValue));
});
console.log((qValue));
}, [qValue]);
return (
<div>
<h2>Screen</h2>
<QuestionSingleMode value={qValue} />
</div>
);
}
function QuestionSingleMode(props){
var [questions,setQuestions]=useState(props.value.questions);
var [renderQuestion,setRenderQuestion]=useState("")
var [counter,setCounter]=useState(props.value.questions.length)
useEffect(()=>{
console.log(questions)
setRenderQuestion(questions[0])
},[renderQuestion])
function nextQuestion(){
setQuestions(questions.splice(0,1))
setRenderQuestion(questions[0])
setCounter(counter--)
}
return(
<div>
<h1>{renderQuestion}</h1>
<button onClick={nextQuestion}>{counter ? "next" : "finish"}</button>
</div>
)
}
Actually I solved the issue by changing the renderQuestion to props.questions in the useEffect() array.

How to test a chart to see if it renders correctly

I need help, I wanted to test whether the graph is rendering to me by searching for the word that should appear after the graph is rendered, but it gets an error.
I am trying to write tests in JEST
Below is a function drawing a graph
export interface CharProps {
data:Array<any>,
labels:Array<any>
}
export const Graph: React.FC<CharProps> = ({labels,data}) => {
const [chartData, setChartData]= useState({})
const chart = ()=>{
setChartData({
labels:labels,
datasets:[
{
label: 'Annual revenue',
fill: false,
}
]
})
}
useEffect(()=>{
chart()
},[])
return (
<>
<div className={chartBackground}>
<Line data={chartData} options={{responsive:true}}/>
</div>
</>
);
}
And my test below
describe('<Graph /> ', () => {
it('should be redner', () => {
render(<Graph data={[65]} labels={['monday']} ></Graph>);
expect(screen.getByText('monday')).toBeTruthy;
});
})
And my bug
TypeError: Cannot set property '_options' of undefined
8 | describe('<Graph /> ', () => {
9 | it('should be redner', () => {
> 10 | render(<Graph data={[65]} labels={['monday']} ></Graph>);
| ^
11 | expect(screen.getByText('monday')).toBeTruthy;
12 | });
13 | })
I cannot understand it, please help.
With the limited context available I can only guess what the problem is. But it seems like Graph is unknown to jest. Please check if you have properly imported the Graph component in your test file or test helper.
More information on jest and react: https://jestjs.io/docs/en/tutorial-react

Resources