Test if a component is passing an #Input to another - javascript

I'm fairly new to unit testing. I wonder how I could test if a smart component is passing some as an #Input() to a dumb one (using Angular 4+).
At first, I thought about checking if the property exists:
it('should have some data', async(() => {
expect(component.data).toBeTruthy();
}));
However, I faced two issues: 1) that tells me if data is true but doesn't necessarily means it's being passed as an input to my dumb component; 2) if the data property doesn't exist, then the test suite won't be executed.
Any tips? Is there a better way to approach this? Thanks.

Since input bindings are processed as part of change detection you can basically change the property used in bindings on the parent component, then run detectChanges() on the parent component and check if the input property has changed in the child component. Something along these lines:
parentComponent = TestBed.createComponent(BannerComponent);
const childComponentEl = fixture.debugElement.query(By.directive(ChildComponent));
const childComponent = childComponentEl.injector.get(ChildComponent);
it('should have some data', async(() => {
parentComponent.componentInstance.boundProperty = 3;
parentComponent.detectChanges();
expect(childComponent.inputProperty).toBe(3);
}));
You can read more about why input bindings update in:
Everything you need to know about change detection in Angular
The mechanics of property bindings update in Angular

Related

Vue child component not displaying dynamic data on first page load

Given the code below, my child component alerts trigger before any of the code in the Parent mounted function.
As a result it appears the child has already finished initialization before the data is ready and therefor won't display the data until it is reloaded.
The data itself comes back fine from the API as the raw JSON displays inside the v-card in the layout.
My question is how can I make sure the data requested in the Parent is ready BEFORE the child component loads? Anything I have found focuses on static data passed in using props, but it seems this completely fails when the data must be fetched first.
Inside the mounted() of the Parent I have this code which is retrieves the data.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
console.log('DATA ...') // fires after the log in Notes component
this.checkAttendanceForPreviousTwoWeeks().then(()=>{
this.getCurrentParticipants().then((results) => {
this.currentP = results
this.notesArr = this.notes // see getter below
})
The getter that retrieves the data in the parent
get notes() {
const newNotes = eventsModule.getNotes
return newNotes
}
My component in the parent template:
<v-card light elevation="">
{{ notes }} // Raw JSON displays correctly here
// Passing the dynamic data to the component via prop
<Notes v-if="notes.length" :notesArr="notes"/>
</v-card>
The Child component:
...
// Pickingn up prop passed to child
#Prop({ type: Array, required: true })
notesArr!: object[]
constructor()
{
super();
alert(`Notes : ${this.notesArr}`) // nothing here
this.getNotes(this.notesArr)
}
async getNotes(eventNotes){
// THIS ALERT FIRES BEFORE PROMISES IN PARENT ARE COMPLETED
alert(`Notes.getNotes CALL.. ${eventNotes}`) // eventNotes = undefined
this.eventChanges = await eventNotes.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
...
I am relatively new to Vue so forgive me if I am overlooking something basic. I have been trying to fix it for a couple of days now and can't figure it out so any help is much appreciated!
If you are new to Vue, I really recommend reading the entire documentation of it and the tools you are using - vue-class-component (which is Vue plugin adding API for declaring Vue components as classes)
Caveats of Class Component - Always use lifecycle hooks instead of constructor
So instead of using constructor() you should move your code to created() lifecycle hook
This should be enough to fix your code in this case BUT only because the usage of the Notes component is guarded by v-if="notes.length" in the Parent - the component will get created only after notes is not empty array
This is not enough in many cases!
created() lifecycle hook (and data() function/hook) is executed only once for each component. The code inside is one time initialization. So when/if parent component changes the content of notesArr prop (sometimes in the future), the eventChanges will not get updated. Even if you know that parent will never update the prop, note that for performance reasons Vue tend to reuse existing component instances when possible when rendering lists with v-for or switching between components of the same type with v-if/v-else - instead of destroying existing and creating new components, Vue just updates the props. App suddenly looks broken for no reason...
This is a mistake many unexperienced users do. You can find many questions here on SO like "my component is not reactive" or "how to force my component re-render" with many answers suggesting using :key hack or using a watcher ....which sometimes work but is almost always much more complicated then the right solution
Right solution is to write your components (if you can - sometimes it is not possible) as pure components (article is for React but the principles still apply). Very important tool for achieving this in Vue are computed propeties
So instead of introducing eventChanges data property (which might or might not be reactive - this is not clear from your code), you should make it computed property which is using notesArr prop directly:
get eventChanges() {
return this.notesArr.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
Now whenever notesArr prop is changed by the parent, eventChanges is updated and the component will re-render
Notes:
You are overusing async. Your getNotes function does not execute any asynchronous code so just remove it.
also do not mix async and then - it is confusing
Either:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray)
await this.checkAttendanceForPreviousTwoWeeks()
const results = await this.getCurrentParticipants()
this.currentP = results
this.notesArr = this.notes
or:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
Promise.all(promisesArray)
.then(() => this.checkAttendanceForPreviousTwoWeeks())
.then(() => this.getCurrentParticipants())
.then((results) => {
this.currentP = results
this.notesArr = this.notes
})
Great learning resource

Vuejs composition API - property changes

I'm curious about passing props into setup and what are best practices to update variables/templates based on property changes.
I'm curious about reactive and computed.
For example:
setup(props) {
// Setup global config settings
const config = computed(() => {
return {
// See if the component is disabled
isDisabled: props.disabled, // (1)
// Test for rounded
isRounded: props.rounded // (2)
}
})
return { config }
}
Should config.isDisabled and config.isRounded be wrapped in their own computed function as they are both different and independent? However, it is easy to just stick them into one big function. What is best practice in this regard?
Does the entire config function evaluate once a single property changes within the function or can it recognize the change and update what is required?
Per docs, reactive is deeply reactive and used for objects, however, I've noticed it doesn't update to property changes. Therefore, I've been treating it more like data in Vue 2. Am I missing something or is this correct treatment?
You do not have to wrap props with computed at all, as they should be already reactive and immutable.
You also do not have to return config from your setup function as all props passed to your component should be automatically exposed to your template.
The computed function is evaluated only once and then Vue3 uses Proxy to observe changes to values and update only what's required. If you need to run a function every time a property changes you can use watchEffect.
Vue3 reactive is actually deep and works fine on objects. It should track all changes, unless you are trying to change the original object (the target of reactive function).

Vue V-Bind to Programmatically Created Instance

I follewed this instructions to create a Vue instance programmatically. I use this to dynamically add component instances in project by user events. My problem now it, that my component to initialize needs a model. I regular I would use it like this:
<my-component v-model="variable"/>
But now I create this component with this code snippet within another components methods section:
import MyComponent from '../MyComponent'
...
add () {
const Component = Vue.extend(MyComponent)
const instance = new Component()
instance.$mount()
document.getElementById('app').appendChild(instance.$el)
}
I know using a $ref here is better, but it must work globally, so I didn't know how to add it else to the DOM. But just as side note.
Now i need to give this instance a v-model binding. I already know how to define props or slots, but not a model. In the official docu they mention something for that. But to be honest I don't understand it and didn't get it work.
Can anybody tell me how I have to extend my code to define the model for this instance? Something like instance.$model = this.variable would be awesome. Thank u!
Finally I got some kind of workaround for this. I'm not aware if there is a better solution out there, but this works for me.
The MyComponent used this description to handle the v-model. By this is emit the change event for the parent component. So The idea is simply to pass the model variable as property, work in MyComponent on a copy of this variable and emit changes to the parent. To catch this change event I can add to my instance the following:
const Component = Vue.extend(EditWindow)
const instance = new Component({
propsData: { content: this.variable }
})
instance.$on('change', value => {
this.variable = value
})
instance.$mount()
document.getElementById('app').appendChild(instance.$el)
I guess this is pretty the same as Vue actually does in the background (maybe?). But after all it works and I'm happy. Of cause I'm open for the 'correct' solution, if such one should exist.

VueJs: How to pass a 'computed function' value from child component to the parent component using $emit(optionalPayload) and $on?

I am new to VueJs and I am doubtful about passing the optional payload
Could anyone please tell me how to pass the value returned by a computed function in the child component to the parent component using the optional payload.
I want to implement a separate independent search component which returns the search results to all other components. The computed function looks like this:
get computedSports () {
if (!this.searchModel || this.searchModel.length === 0)
return this.sports
else
return this.fuseSearch.search(this.searchModel)
}
This is how I am trying to pass the value returned by computed function to its parent component in the child template:
#input="$bus.$emit('computed-sports', computedSports)"
In the parent component, this is how I am trying to access the value of the child's computed function:
v-on:computed-sports=""
I am not quite sure how to access the value here. Could anyone help me out in this?
Thanks!
The argument to a v-on should be a method or inline function that takes the payload as an argument. For example
v-on:computed-sports="handleComputedSports"
and your method might be defined
handleComputedSports(theValue) {
console.log("The value is", theValue);
}
There's an example in this section of the documentation. Your emit is fine. The fact that the value comes from a computed makes no difference to anything.

how to use watch function in vue js

could you please tell me how to use watch function in vue js .I tried to used but I got this error.
vue.js:485 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "m"
found in
---> <AddTodo>
<Root>
https://plnkr.co/edit/hVQKk3Wl9DF3aNx0hs88?p=preview
I created different components and watch properties in the main component
var AddTODO = Vue.extend({
template: '#add-todo',
props: ['m'],
data: function () {
return {
message: ''
}
},
methods: {
addTodo: function () {
console.log(this.message)
console.log(this.m);
this.m =this.message;
},
},
});
When I try to add item I am getting this error.
Step to reproduce this bug
Type anything on input field and click on Add button
this.m =this.message;
this line is the issue,
It's recommended that you don't modify prop directly...
instead create a data property and then modify it.
It shows warning because you're modifying the prop item, prop value will be overwritten whenever the parent component re-renders.
The component's props are automatically updated in the component as soon as you change their value outside of it.
For this reason, trying to change the value of a property from inside your component is a bad idea: you should use the props as read-only.
If you want to use a prop as the initial value of some of your component's data you can simply declare it this way:
data: function () {
return {
changeable: this.receivedProp;
}
},
That being said, if you are trying to change the value of a prop from inside a component to be able to use your reassigned prop outside of it, you are doing it the wrong way. The way you should handle this is by using Vue's custom events.
Remember, as Vue's documentation states:
In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events.

Categories