Testing Library (VueJS) - Rendering multiple components in a it block - javascript

Relatively new to testing so this is probably super simple:
I want to test a checkbox component. I got the basics down, however, how can I render multiple components inside an it block?
My code so far. I am stuck on the second test, where I want to render multiple items and check one. That would should return its value (or however we can test the selected checkbox).
The component itself it quite simple. It has a label and checkbox element and receives all the props you would expect.
Thank you!
import { render } from '#testing-library/vue';
import OBCheckbox from '../OBCheckbox.vue';
describe('OBCheckbox', () => {
it('should be selected', async () => {
const label = 'checkboxLabel1';
const value = 'Testing Value';
const { getByLabelText } = render(OBCheckbox, { props: { label, value } });
const checkBox = getByLabelText(label);
expect(checkBox).toBeChecked(value);
});
it('should return selected items value', async () => {
// stuck here
});
it('should be disabled', async () => {
const label = 'checkboxLabel1';
const value = 'Testing Value';
const disabled = true;
const { getByLabelText } = render(OBCheckbox, { props: { label, value, disabled } });
const checkBox = getByLabelText(label);
expect(checkBox).toBeDisabled();
});
it('should be accessible', async () => {
const label = 'checkboxLabel1';
const value = 'Testing Value';
const { getByRole } = render(OBCheckbox, { props: { label, value } });
const checkBox = getByRole('checkbox');
expect(checkBox).toBeChecked(value);
});
});

I'm not sure what multiple components you are trying to render in one it block but if what you are trying to do is rendering multiple OBCheckbox in one it block, then probably you can do something like this.
import { screen, render } from '#testing-library/vue';
it('should return selected items value', () => {
render({
template:`
<div>
<your_component :label="'label1'" :value="'value1'"/>
<your_component :label="'label2'" :value="'value2'"/>
<your_component :label="'label3'" :value="'value3'"/>
</div>
`,
components:{ your_component }
})
// assuming the label of the component is associated with its input by input's id
const selectedCheckbox = screen.getByRole('checkbox', { name: 'label1' })
expect(selectedCheckbox).toBe(...)
})

Related

Notify other component by raising a manual event

A have two files, with two functional components A and B, in the first component A, i have a specialFunction that gets called with onClick, what i want to do is raise an event in specialFunction when it's called, and then in component B add a Listener for the event in specialFunction.
Component A:
function specialFunction(){
//raise the event and send some data
}
Component B:
//contains a listener that does some work when specialFunction is called, example:
(data) => {console.log("am called:",data)};
1. Create notifier class using observer pattern
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
2. ComponentA receives notifier as a prop and used to notify all subscribers
const ComponentA = ({ notifier }) => {
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div>{/** Some content */}</div>
}
3. ComponentB receives notifier and subscribes to it to receive data sent by from ComponentB
const ComponentB = ({ notifier }) => {
useEffect(() => {
const callbackFn = data => {/** Do whatever you want with received data */ }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [])
}
4. App holds both component. Create instance of notifier there and pass as a props
const App = () => {
const dataNotifier = new ChangeNotifier();
return <div>
<ComponentA notifier={dataNotifier} />
<ComponentB notifier={dataNotifier} />
</div>
}
If you have components on different levels deeply nested and it is hard to pass notifier as a prop, please read about React Context which is very helpful when you want to avoid property drilling
React Context
Here's implementation with context
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
return this.unsubscribe.bind(this, callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
const NotifierContext = React.createContext();
const ComponentA = () => {
const { notifier } = useContext(NotifierContext);
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div><button onClick={triggerNotifier}>Notify</button></div>
}
const ComponentB = () => {
const { notifier } = useContext(NotifierContext);
useEffect(() => {
const callbackFn = data => { console.log(data) }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [notifier])
}
Now all components wrapped in NotifierContext.Provider (no matter how deep they are nested inside other components) will be able to use useContext hook to receive context value passed as value prop to NotifierContext.Provider
const App = () => {
const dataNotifier = useMemo(() => new ChangeNotifier(), []);
return <NotifierContext.Provider value={{ notifier: dataNotifier }}>
<ComponentA />
<ComponentB />
</NotifierContext.Provider>
}
export default App;
Last but not least, I guess you can avoid context or properties drilling and just create instance of ChangeNotifier in some utility file and export it to use globally...
Andrius posted a really good answer, but my problem was that the two components, one of them is used as an API, and the other had a parent component, am a beginner so maybe there is a way to use them but i just didn't know how.
The solution that i used, (maybe not the best) but did the job was to dispatch a custom event in a Promise from the specialFunction:
function specialFunction(){
new Promise((resolve) => {
console.log("am the promise");
document.dispatchEvent(event);
resolve();
});
And add a Listener in the other component using a useEffect hook:
useEffect(() => {
let handlePreview = null;
new Promise((resolve) => {
document.addEventListener(
"previewImg",
(handlePreview = (event) => {
event.stopImmediatePropagation();
//Stuff...
})
);
return () =>
window.removeEventListener("previewImg", handlePreview, false);
});
}, []);
Thank you for your help.

how can i refactor this code snippet from a class to functional component react?

i have this code inside a class, how could i keep the idea of ​​it but updating to use in a function component? I'm trying to change but I can't keep the current proposal
validate = value => {
const {
formApi: { getValue },
name,
} = this.props;
const component = (props) => {
const validate = value => {
const {
formApi: { getValue },
name,
} = props
}
}
const Component = ({formApi: {getValue}, name})=> {
const validate = useCallback((value)=> {
}, [getValue, name]);
}

React unit test with spyOn on useState is not calling mocked function as it should

I'm learning about testing in React, I have a component with input to write what to search, a select box to select where to search and a button to order by whats selected in the select box.
I made a test to check if when I changed the option of select box, to see if it called the setFilterBy function. It goes inside the function and changes as predicted from "title" to "author" but it doesn't detect the call of the mocked function. Anyone knows whats wrong with this?
Component
import React, { useState } from "react"
import PropTypes from "prop-types"
interface Props {
handleFilterChange: (event: React.ChangeEvent<HTMLInputElement>, filterBy: string) => void
handleSortClick: (filterBy: string) => void
total: number
filtered: number
}
export const PostFilter: React.FC<Props> = ({ handleFilterChange, handleSortClick, total, filtered }) => {
const [filterBy, setFilterBy] = useState<string>("title")
const handleSelect = (event: React.ChangeEvent<HTMLSelectElement>) => {
console.log("a")
setFilterBy(event.target.value)
}
return (
<div>
<label> Filter </label>
<input className="search-input" type="text" name="search" onChange={event => handleFilterChange(event, filterBy)}/>
<span style={{ padding: "4px" }}>{ filtered > 0 && `${total - filtered} posts found.` } </span>
By
<select className="search-type" style={{ margin: "4px" }} value={filterBy} onChange={handleSelect}>
<option value="title">Title</option>
<option value="author">Author</option>
</select>
<button className="sort-button" onClick={event => handleSortClick(filterBy)}> Sort </button>
</div>
)
}
Test
import React from "react"
import { mount } from "enzyme"
import { PostFilter } from "./PostFilter"
const handleFilterChange = jest.fn()
const handleSortClick = jest.fn()
const setState = jest.fn()
describe("PosFilter", () => {
let wrapper
beforeEach(() => {
jest.spyOn(React, 'useState').mockImplementation(initState => [initState, setState]);
wrapper = mount(
<PostFilter handleFilterChange={handleFilterChange} handleSortClick={handleSortClick} total={10} filtered={4} />
);
});
it("Changing select button should call setState", () => {
const select = wrapper.find(".search-type")
expect(select.instance().value).toBe("title")
wrapper.find(".search-type").simulate('change', {
target: {
value: "author"
}
})
expect(select.instance().value).toBe("author")
expect(setState).toHaveBeenCalledTimes(1);
})
});
Result:
console.log src/components/threads/PostFilter.tsx:15
a
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
You should not be testing if useState has been used within your component. Tests should ensure the behaviour of the component is the expected one, without taking into account its implementation details.
In this particular case, you use useState to control the value of the select element, and you are already testing that once the user selects a new value, its value property changes accordingly:
it("Changing select button updates its value", () => {
const select = wrapper.find('.search-type');
expect(select.prop('value')).toBe('title');
select.simulate('change', {
target: {
value: 'author'
}
});
expect(wrapper.find('.search-type').prop('value')).toBe('author');
});
That should be it as far as the test goes. If you later change the implementation to set the value property in the select but maintain its functionality (say, for example, that you change your component and define it as a class component and use the setState method within the class to update the select value), the test will continue to work.
That being said, if you still want to check if useState is being used, you will have to mock the whole react module with jest.mock at the start of your test file:
import React from 'react';
import { mount } from 'enzyme';
import { PostFilter } from '../PostFilter';
const handleFilterChange = jest.fn();
const handleSortClick = jest.fn();
const setState = jest.fn();
jest.mock('react', () => {
const actualReact = jest.requireActual('react');
return {
...actualReact,
useState: jest.fn()
};
});
describe('PosFilter', () => {
let wrapper;
beforeEach(() => {
jest.spyOn(React, 'useState').mockImplementation(initState => [initState, setState]);
wrapper = mount(
<PostFilter
handleFilterChange={handleFilterChange}
handleSortClick={handleSortClick}
total={10}
filtered={4}
/>
);
});
it('Changing select button should call setState', () => {
const select = wrapper.find('.search-type');
expect(select.prop('value')).toBe('title');
select.simulate('change', {
target: {
value: 'author'
}
});
expect(setState).toHaveBeenCalledTimes(1);
expect(setState).toHaveBeenCalledWith('author');
});
});
Note that now you can't check if the value of your select element has changed, because you are mocking the returned method in useState. This causes that when the mock version is called, the default behaviour (updating the state and re-rendering the component) does not take place.

Why is my onChange event not working in React?

I am coding a simple search input component for an app that will eventually become larger, but I am at a loss for why the onChange prop associated with it isn't being called. Here I will show my search input component and the app component into which I import it:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class SearchInput extends Component {
static defaultProps = {
onChange: () => Promise.resolve(),
}
static propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<input className="search-input" type='text' onChange={this.handleChange} value={value}/>
)
}
handeChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
}
And then here's my main app component (very simple still, and keep in mind that I have list-rendering functionality, but that isn't where my issue lies). I'm pretty sure the issue lies somewhere in the handleSearchDidChange method that I wrote up and tacked onto the onChange prop for the SearchInput component:
import React, { Component } from 'react';
import Container from './components/container'
import List from './components/list'
import SearchInput from './components/search-input';
// Styles
import './App.css';
export default class App extends Component {
constructor(props){
super(props)
this.state = {
searchValue: undefined,
isSearching: false,
}
// this.handleSearchDidChange = this.handleSearchDidChange.bind(this);
}
render() {
// in the main render, we render the container component (yet to be styled)
// and then call renderList inside of it. We need "this" because this is
// a class-based component, and we need to tell the component that we are
// using the method associated with this class
return (
<div className="App">
<Container>
{this.renderSearchInput()}
{this.renderList()}
</Container>
</div>
);
}
renderSearchInput = () => {
const { searchValue } = this.state;
return (<SearchInput onChange={this.handleSearchDidChange} value={searchValue}/>)
}
renderList = () => {
// return the list component, passing in the fetchData method call as the data prop
// since this prop type is an array and data is an array-type prop, this is
// acceptable
return <List data={this.fetchData()}/>
}
// possibly something wrong with this method?
handleSearchDidChange = (e) => {
const { target } = e;
const { value } = target;
this.setState({
searchValue: value,
isSearching: true,
});
console.log('value: ', value);
console.log('searchValue: ', this.state.searchValue);
console.log('-------------------------')
}
fetchData = () => {
// initialize a list of items
// still wondering why we cannot put the listItems constant and the
// return statement inside of a self-closing setTimeout function in
// order to simulate an API call
const listItems = [
{title: 'Make a transfer'},
{title: 'Wire money'},
{title: 'Set a travel notice'},
{title: 'Pop money'},
{title: 'Edit travel notice'},
{title: 'test money things'},
{title: 'more test money things'},
{title: 'bananas'},
{title: 'apples to eat'},
{title: 'I like CocaCola'},
{title: 'Christmas cookies'},
{title: 'Santa Claus'},
{title: 'iPhones'},
{title: 'Technology is amazing'},
{title: 'Technology'},
{title: 'React is the best'},
];
// return it
return listItems;
}
You have a typo! Missing the "l" in handleChange :)
handleChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
i run your code in sandBox:
https://codesandbox.io/s/onchange-problem-37c4i
there is no issue with your functionality as far as i can see.
but in this case if onChange dose not work for you is because maybe inside of < SearchInput /> component you don't pass the value up to the parent element.
check the sandBox and notice to the SearchInput1 and SearchInput2

How to test if component counts array length properly with Jest

I have a component that I am giving an array with objects as props to it like this:
describe('component', () => {
it('should return the correct number of items passed in the array', () => {
const comp = shallowMount(component, {propsData: {
buttons: [
{name:'button1'},
{name:'button2'}
]
}});
expect(component.length).toHaveLength(buttons).length
});
});
How can I test that the provided array has the correct length, for example if there are two objects in the array, the component should return two, if there is one, one, if there are none then it should return 0, how can I achieve that? I tried
expect(component.length).toHaveLength(buttons).length
But that does not work
I guess you want to check if the correct number of childs of some type was rendered (in Vue).
// import component that you want to count, e.g. Button
const buttons = [
{ name: 'button1' },
{ name: 'button2' }
]
const comp = shallowMount(component, { propsData: { buttons } })
expect(comp.findAll(Button).length).toBe(buttons.length)
https://lmiller1990.github.io/vue-testing-handbook/finding-elements-and-components.html#findall
const buttons = [
{name:'button1'},
{name:'button2'}
]
const comp = shallowMount(component, {propsData: { buttons }});
expect(comp.findAll(Button)).toHaveLength(buttons.length)

Categories