How to test if component counts array length properly with Jest - javascript

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)

Related

How to make child components use map() from App.js props data?

My react is 18.2. I want the child component to receive data and use map() from App.js.
I checked the child component received the data, but I don't know why does it can't use map().
It shows this error.
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
So do anyone can fix it? I guess this is render problem, but I don't know how to fix it, thank you.
fetched():
[
{ id:1, date: '2011-01-01T00:00:00.000Z' },
{ id:2, date: '2013-09-03T00:00:00.000Z' },
{ id:3, date: '2012-04-02T00:00:00.000Z' },
{ id:4, date: '2013-12-08T00:00:00.000Z' },
{ id:5, date: '2010-01-23T00:00:00.000Z' },
];
App.js:
import { Child } from './Child';
const Test = () => {
const [toChild, setToChild] = useState()
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
const test = () => {
setToChild(fetchedData)
}
}
return(<Child toChild={toChild}>)
Child.js:
const Child = ({ toChild }) => {
const data = toChild;
const getDate = data.map((item) => item.date);
Since the data coming from your fetch is an array of objects, you may want to initialize the state as an empty array useState([]), useEffect will fire once the component has been mounted so at first, toChild will be undefined and that's why you are getting that error.
So basically:
import { useState, useEffect } from 'react'
import { Child } from './Child';
const Test = ()=>{
const [toChild,setToChild] = useState([])
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
return <Child toChild={toChild} />
Jsx is already rendered before javascript in react even though you fetch the data from server but html already rendered to the browser.so add any loading component to that. toChild?:<div>loading</div>:<Child toChild={toChild}/>

Vue: Convert code from Options to Composition API

I am trying to convert the following code form options to composition API in Vue using Typescript.
data() {
return {
items: [
'Car',
'Truck',
'Bike',
'Caravan'
],
activeItem: 0,
show: false,
};
},
methods: {
showDropdown() {
this.show = !this.show;
},
setActiveItem(item) {
this.activeItem = this.items.indexOf(item);
this.show = false;
}
},
computed: {
dropdownItems() {
return this.items.filter((item,i) => i !== this.activeItem)
}
}
This is what I have. I am still new to Vue and Typescript so i am using this example to learn more about composition API and Typescript.
setup() {
const activeItem = 0;
const show = ref(false);
const items = ref([
'Car',
'Truck',
'Bike',
'Caravan'
]);
function showDropdown(this: any) {
this.show = !this.show;
}
function setActiveItem(this: any, item: string) {
this.activeItem = this.items.indexOf(item);
this.show = false;
}
const dropdownItems = computed(function (this: any, item: string) {
return this.items.filter((item, i) => i !== this.activeItem);
});
return {
activeItem,
show,
items,
showDropdown,
setActiveItem,
dropdownItems,
};
},
The errors I am getting are for example in the setActiveItem method is 'this' implicitly has type 'any' because it does not have a type annotation. So when I pass this: any params it works but I don't know if this is the right way to do it?
Second problem is I can't get the computed method to work I don't know how to implement it correctly. Is there someone who can help me out with this?
The objective of composition API is to get rid of dynamic this that isn't an instance of a specific class but an object that aggregates properties from component definition and puts limitations on TypeScript typing.
Instead, variables defined in the scope of setup are supposed to be accessed directly:
const dropdownItems = computed((item: string) => {
return unref(items).filter((item, i) => i !== unref(activeItem));
});
activeItem is supposed to be a ref as well.
Computed method you just import import { ref, computed } from "#vue/reactivity";. With ref you need to use value. (Following code is without typescript)
import { ref, computed } from "#vue/reactivity";
setup() {
const activeItem = ref(0);
const show = ref(false);
const items = ref([
'Car',
'Truck',
'Bike',
'Caravan'
]);
const dropdownItems = computed(() => items.value.filter((item, i) => i !== activeItem.value));
const showDropdown = () => {
show.value = !show.value;
}
const setActiveItem = (item) => {
activeItem.value = items.value.indexOf(item);
show.value = false;
}
return {
activeItem,
show,
items,
showDropdown,
setActiveItem,
dropdownItems,
};
},

Check all nested boxes when parent is selected

I have the following array I am working with
const data = [
{
name: "Person",
id: 0,
familyMembers: [
{
id: 00,
name: "personOne"
},
{
id: 01,
name: "personTwo"
}
]
},
]
I am dynamically mapping over this array with one map for the main objects and another for the nested array. I have a toggle and I am trying to implement functionality that when the parent is selected it automatically selects the nest kids of said parent as well.
const [isSelected, setIsSelected] = useState({});
const handleCheck = (name: string, event: any): void => {
setIsSelected({ ...isSelected, [name]: event.target.isSelected });
};
Well, it took me some time to apply it :)
First of all, there isn't any isSelected prop for the CheckBox component and it should be changed to checked as below:
checked={isSelected[id]}
Then, handleCheck function in dataList was not called properly and it should be changed as below:
dataList(
nestedItem.name,
nestedItem.id,
true,
isSelected,
(name: string, event: any | undefined) =>
handleCheck(event, name)
)
we need to also initialize values in useState otherwise we would receive this error:
component is changing an uncontrolled input to be controlled
Finally, in the handleCheck function I checked whether data[name] exist and then mapped over data[name].familyMembers and stored checked values in the isSelected object as below:
const handleCheck = (name: string, event: any): void => {
var nested = {};
if (data[name]) {
data[name].familyMembers.map(
(el, id) => (nested = { ...nested, [el.id]: event.target.checked })
);
}
setIsSelected({ ...isSelected, [name]: event.target.checked, ...nested });
};
You can also check these corrections online here in sandbox

How to change specific properties on object array?

I have question about changing properties inside the object array. I will post my code below for better understanding. First, I have object array that contains two array and first array contain attribute called "first" and second array contains "last". When I click on the function it will pass the specific name such as first or last as string. After that I have to loop through the object array and change the value that matches the properties name (tempFirst,tempLast). I just declared inside the onHandle function but on my real project, I am passing through the parameters.
example would be: if (first === first) change the value of that property.
import logo from './logo.svg';
import './App.css';
import react ,{ Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
objectTemp: []
};
}
componentDidMount(){
let objectArray = [
{
name: "hello",
first: 77
},
{
name: "world",
last: 66
},
]
this.setState({
objectTemp: objectArray
})
}
onHandle = () => {
let tempFirst = "first";
let tempLast = "last";
let storeState = [...this.state.objectTemp];
//console.log(storeState);
// here i want to check if either tempfirst or templast is equal to the property on object array
for(let i = 0; i < storeState.length; i++){
//if(storeState[i] === tempLast){
console.log(...storeState[i]);
//}
}
}
render() {
console.log(this.state);
return <button onClick={this.onHandle}>VIEW</button>;
}
}
export default App;
I think the step for your modification is:
onHandle = (firstName: string, value: any) => {
/// Modify your data:
const newData = this.state.objectTemp.map( item => {
/// Update Data if matched condition
if(item.firstName === firstName){
item.value = value
}
return item
})
/// Update state
this.setState({objectTemp: newData})
}
You can map the previous state to new state, and when mapping each element object, check each key against a "searchKey" and if you find a match update the value.
Here's an example onHandle function that maps the objectTemp state and creates an array of object entries (i.e. array of key-value pairs) of the objects. It reduces the entries array back into an object, and while doing so checks the object keys for the match.
onHandle = (searchKey, newValue) => {
this.setState(prevState => ({
// map previous state to next state
// [obj1, obj2, ...objN]
objectTemp: prevState.objectTemp.map(obj => {
// Create array of entries and reduce back into object
// [[key1, value1], [key2, value2], ...[keyN, valueN]]
return Object.entries(obj).reduce((obj, [key]) => {
// If key is a match to search key, create new object and update value,
// otherwise return existing object
return key === searchKey
? {
...obj,
[key]: newValue
}
: obj;
}, obj);
})
}))
}
Demo

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

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(...)
})

Categories