vue-test-utils finding button does not work with find option object - vue.js

According to the docs an element of a wrapper can be found by name.
Using a find option object, Vue Test Utils allows for selecting elements by a name of component on wrapper components.
and the example given is
const buttonWrapper = wrapper.find({ name: 'my-button' })
buttonWrapper.trigger('click')
In my vue component I have three buttons. I want to test function of all three buttons, but I don't want to use wrapper.findAll("button").at(1) to select the second button, because when the order of the buttons is changed, the test fails. So I decided to name each button and find them by name using the option object.
In my component I have:
<button class="button is-success" name="save-button">Opslaan</button>
In my test I have:
const button = wrappedAddSubject.find({name:"save-button"})
This returns undefined instead of the button. What am I doing wrong?

I think "name" refers to the name of the component, not the attribute "name" of html element.
For ex, if you have a component:
Vue.component('save-button', {
template: "<button>Click</button>
})
you can find this component by its name using your syntax.

Related

How to add a click function to an imported component in Vue

So I have a Vue2 app. I have create a component "u-button"
when i import this and use it in another component, I want to be able to add a click function to it. However at the moment it looks for a function on the u-button component rather than the component it is being used in.
so for example, in the below if i click the first button nothing happens, if i click the second button i get the console log.
<template>
<div>
<u_button #click="clicked">Click me</u_button>
<button #click="clicked">Click me</button>
</div>
</template>
<script>
import u_button from '../components/unify/u_button'
export default {
components: {
u_button
},
methods: {
clicked() {
console.log("Working!");
}
}
}
</script>
However if i add a method on the u-button component, then it calls that. So how can i get my below example to work ? The only thing I can think of is to wrap it in another div and add the click function to that. but I'm wondering if there is a better way?? I dont want to use events to do this either as that gets messy very quickly.
As you can imagine having a reusable button that when clicked always performs the same function is a bit pointless.
It's because of the nature of components for example if we had a (virtual) iframe component which had a button in it and we'd like to detect click event on it we might name the event click and listen for it in the parent component; therefore, Vue introduced a feature called event modifiers for example in Vue, We have .native modifier (you can read more about the Vue modifiers here)
Now, to make your code work, You should add a .native after #click like this:
<u_button #click.native="clicked">Click me</u_button>
By the way, it's better to develop a naming convention for yourself It'd become handy when your projects get larger.

Vue multiples components

I'm doing a project with ElementUI Tabs (just HTML and JS files, no .vue files) and I want to open a new Tab, and add html inside, like I've always used to do in Jquery and SemanticUI, for example, the user clicks the menu called "Person" and the Person View (a Vue component) opens in the tab (id = "tab1") to add a new person register, and if the user clicks again the "Person" menu, another tab opens (id = "tab2") with the Person View.
First Question: Because the Vue Component has no "el:" selector, how can I tell to component (Person View) to open inside the "tab1", and another click to open inside the "tab2" ? There is any selector like "el" in Vue.component()?
Second Question: Using Vue instance ( new Vue ({options}) ), it works, because is possible to use the selector "el", but I've read before in some blogs, that is not good practice, because the app must have only one instance of Vue. Is correct add more than one Vue instance ( new Vue () ) as used to be done adding many Vue.component ({}) in the project?
Third Question: I've read before that Vue.component() is a Vue instance, and so would be correct to say that Vue.component() and Vue() is the same thing, but with different sintax ?
Question 1:
Actually, a component does have an el. Your template determines what el is.
For example, I created an inline template for my select2 that look like this:
<select2>
<select></select>
</select2>
Vue.componet("select2", {blah blah blah});
in this case el is the select box directly.
If I did:
<select2>
<div>
<select></select>
</div>
</select2>
the component el would be the div.
Question 2: what you heard from those blogs is nonsense, at least as far as Vue 2 is concerned (never worked with ver 1)
You, as a coder, determine what el is in your code so it is safe to use as a selector. I do it all of the time.
Vues cannot overlap but you can have as many on a page as makes sense. On one set of my tabs, each tab is completely different from each other and independent of each other so each has its own Vue instance. On another, each tab is the same so a made a single component and generated it inside each tab as part of the parent Vue instance.
question 3:
Think of Components as parts and the Vue instance as the whole containing the parts. I personally use components to reduce and compartmentalize code. For example, I have a DataTables component, a select2 component and a tab component, in all cases I have a number of each on each page. Then all I need to do is include them in my Vue instance definition.
After almost two weeks trying, I got it !
First i created an object that has a component structure in a JS file
(personview.js) that i load with requireJS, and pass as a parameter to a
method of Vue Instance called appVue:
appVue.addComponent(componentName,{name:"personview",template:"<div>html tags...</div>",methods:...});
In appVue i added the method:
var appVue=new Vue({
el:'#app',
data() {
return {
components: {},
instances: {}
}
},
methods: {
addComponent(componentName,componentBody){
this.$data.components[componentName]=Vue.component(componentName,Vue.extend(componentBody));
}
}
}
When the user clicks on menu, the method openViewByClickOnMenu is called
and executes:
methods: {
openViewByClickOnMenu(){
//id to identify the components and scripts to load
var componentName="personView"; //for this example i forced the name
//call a method that adds the new tab, and inside the tab adds
//<div id="divX"></div> and return the counter ever increased.
//X in id attribute is the number genereate by the counter
var ctTab=body.addTab({label:menuItem.label});
// will be used to identify an instance of compoment
var componentId=componentName+ctTab; //will be personView1, personView2, etc..
// will be used to identify the div where i want to show component
var divTabId="div"+ctTab;
//load the personview.js with component body
requirejs([componentName],function(){
//creates a new instance of component
app.$data.instances[componentId]=new app.$data.componentes[componentName];
//mounts the component in the div that i want
app.$data.instances[componentId].$mount("#"+divTabId);
});
}
I think the Vue team could add a method in Vue instance to add
components dinamically more easily, sometimes there's no need to
load all html and js files because the user has no acess/permissions
to see some views. And i miss a way to load html native, like
Angular does, because sometimes we need generate html from template engine
inside a SpringBoot for example.

How to define Vue Events on Render Method using createElement

I'm using ElementUi NavMenu, and render function with the createElement method to make the items of the menu just using JSON with titles and index of the menu, just HTML and JS files, not .vue files.
The menu is mounted, the submenus are shown when I click it, but the actions of the submenu (el-menu-item) does not work. I even try the attributes click, item-click, v-on: click when creating the-menu-item (the documentation of ElementUi tells that #click must be used, but this causes an error on createElement when the attributes are defined), but no one works, no error occurs, as if the method was not been declared.
Only onclick attribute works on the el-menu-item, but when I use it, the method of vue component is not called, and so I have to make a function outside of component (on a class for example), and when this function is called it performs a call to component method (I try $ emits) and an error occurs, because the method of component is not found.
How can I add #click (or similar) event on the el-menu-item inside render function of the component to call a method of the same component?
Documenation of NavMenu of ElementUI.
How I'm creating menu item:
createElement("el-menu-item",{
attrs:{
index:json[i].id,
click:json[i].onclick
}},
json[i].title
)
Actually, this is mentioned in Vue.js documentation.
See https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth .
e.g. https://codepen.io/jacobgoh101/pen/ypjGqw?editors=0010
Vue.component("test", {
render: function(createElement) {
return createElement(
"button",
{
on: {
click: function() {
alert('click');
}
}
},
"Header"
);
}
});

Multiple instances of a vue.js component and a hidden input file

I am experiencing a weird behavior using Vue.js 2.
I have a component that I reference twice in a single html page.
This component contains an input file control called attachment_file. I hide it using the Bootstrap class hidden and I open the file selection using another button. When a file is selected, I put in a variable called attachment_filename a certain string just like so:
<template>
<div>
<button #click="selectAttachement"><span class='glyphicon glyphicon-upload'></span></button>
<input id="attachment_file" type="file" class="hidden" #change="attachmentSelected">
{{attachment_filename}}
</div>
</template>
<script>
export default {
data () {
return: {
attachment_filename: null,
}
},
methods: {
selectAttachement () {
$('#attachment_file').click();
},
attachmentSelected () {
this.attachment_filename = 'some file here';
},
}
}
</script>
Problem With the class hidden and when a file is selected from the 2nd instance of the component, the value of this.attachment_filename is updated but in the data of 1st instance of the component!
If I remove the class hidden, it updates the value in the correct instance.
Possible solution use css opacity or width instead of the class hidden.
But is there a reason for this behavior?
Not sure why it is not working specifically with .hidden, but you have an inherent problem in the code that i think is the cause of the problem.
You are selecting the input using jquery with an id, that creates a couple of problems:
When you use the component twice or more, all these inputs generated by these components will have the same id, which is not what you want since id should be unique
Even if you change it to a class instead of id, it won't work properly since you are selecting the element using jquery, and that will select all the elements with this class, while you want to select just the input in that component.
The solution is to use refs:
<input id="attachment_file" type="file" class="hidden" #change="attachmentSelected" ref="fileInput">
selectAttachement () {
$(this.$refs.fileInput).click();
},

Is it posible to delete `div` from template?

I have a component myHello:
<temlate>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
</template>
And main component:
<h1>my hello:</h1>
<my-hello><my-hello>
After rendering shows this:
<h1>my hello:</h1>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
How to delete <div> ?
With VueJS, every component must have only one root element. The upgrade guide talks about this. If it makes you feel better, you are not alone. For what it's worth the components section is a good read.
With the myriad of solutions to your problem, here is one.
component myHello:
<temlate>
<h2>Hello</h1>
</template>
component myWorld:
<temlate>
<p>world</p>
</template>
component main
<h1>my hello:</h1>
<my-hello><my-hello>
<my-world><my-world>
Vue gives you the tools to do so by creating templates or you can do it by having a parent div with two parent divs as children. Reset the data from the data function. Stick with convention (create templates). It's hard to get used to use Vue when you have a jQuery background. Vue is better
Ex.
data () {
message: 'My message'
}
When you click a button to display a new message. Clear the message or just set the message variable with a new value.
ex. this.message = 'New Message'
If you like to show another div. you should used the if - else statement and add a reactive variable. Ex. showDivOne
data () {
message: 'My message'
showDivOne: true,
showDivTwo: false
}
Add this reactive variables to the parent divs that corresponds to the div.
When clicking the button, you should have a function like...
methods: {
buttonClick () {
this.showDivOne = false
this.showDivTwo = true
}
}
I think you can use v-if directive to controll. Add a flag to controll status to show or hide