shallowMount behaves like mount when lazy loading a component - javascript

I'm testing a Nuxt app with vue-test-utils (version 1.0.0-beta.29), and I'm lazy loading the child component. When testing, I want to shallowMount the parent component to stub the childs (I don't want to render the child component because of its dependencies).
Instead of creating child stubs, the whole component tree is rendering in the shallowMount. If I load the components without lazy loading, shallowMount works as expected.
It seems that the problem has already been faced and solved here: https://github.com/vuejs/vue-test-utils/issues/959
I've tried to pass { shouldProxy: true } as a mounting option, or stubbing manually the component by passing { stubs: ['componentname'] }, but the problem is still happening.
parent component:
<template>
<div id="wrapper">
<div
v-for="item in markets"
class="item-wrapper">
<Child :market="item"/>
</div>
</div>
</template>
<script>
export default {
components: {
Child: () => import('./TimelineItem.vue')
},
props: {
markets: {
type: Array,
default: () => []
}
}
}
</script>
child component:
<template>
<div>
Child
</div>
</template>
<script>
export default {
props: {
market: {
type: Object,
default: () => {}
}
}
}
</script>
Snapshot:
<div id="wrapper">
<div class="item-wrapper">
<div>Child</div>
</div>
<div class="item-wrapper">
<div>Child</div>
</div>
</div>
I expected to have an snapshot like this:
<div id="wrapper">
<div class="item-wrapper">
<child-stub></child-stub>
</div>
<div class="item-wrapper">
<child-stub></child-stub>
</div>
</div>

I also had the same issue and I found working solution for me. Try to import components in your test file and add them in components sections..
shallowMount(Component, { components: { Child })

A workaround could be to use a dynamic component.
<div v-if='!Child'>Loading...</div>
<component v-if='Child' :is='Child' />
<script>
export default {
data: {
Child:null,
},
created(){
import('./TimelineItem.vue').then(it=>this.Child=it);
}
}
</script>

Related

vue.js: create component from string

I have two components named component-1 and component-2, along with this vue.js code:
<template>
<div id="app">
<input class="input" #keyup="update()">
<div class="render"></div>
</div>
</template>
<script>
export default {
methods:{
update () {
document.querySelector(".render").innerHTML=`<component-${
document.querySelector(".input").value
} />`
}
}
}
</script>
Whenever I run the code, the component doesn't render. Is there a way to get this component to render?
This isn't the right way to render components dynamically in Vue - instead you should use :is
<template>
<div id="app">
<input class="input" #keyup="update($event.target.value)">
<component :is="myComponent"></component>
</div>
</template>
<script>
export default {
data() {
return {
myComponent: undefined
}
},
methods:{
update (component) {
this.myComponent = component
}
}
}
</script>
A sandbox demo is here

Is there a vue.js equivalent of ngTemplateOutlet?

Does vue.js have an equivalent of Angular's *ngTemplateOutlet directive? Let's say I have some components defined like this:
<template>
<div id="independentComponent">
Hello, {{firstName}}!
</div>
</template>
<script>
export default {
name: "independentComponent",
props: ['firstName']
}
</script>
...
<template>
<div id="someChildComponent">
<slot></slot>
<span>Let's get started.</span>
</div>
</template>
<script>
export default {
name: "someChildComponent"
}
</script>
I want to be able to do something like this:
<template>
<div id="parentComponent">
<template #indepdentInstance>
<independentComponent :firstName="firstName" />
</template>
<someChildComponent>
<template #indepdentInstance></template>
</someChildComponent>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {
someChildComponent,
independentComponent
},
data() {
return {
firstName: "Bob"
}
}
}
</script>
In Angular, I could accomplish this with
<div id="parentComponent">
<someChildComponent>
<ng-container *ngTemplateOutlet="independentInstance"></ng-container>
</someChildComponent>
<ng-template #independentInstance>
<independentComponent [firstName]="firstName"></independentComponent>
</ng-template>
</div>
But it looks like Vue requires the element to be written to the DOM exactly where it is in the template. Is there any way to reference an element inline and use that to pass to another component as a slot?
You cannot reuse templates like ngTemplateOutlet, but can combine idea of $refs, v-pre and runtime template compiling with v-runtime-template to achieve this.
First, create reusable template (<ng-template #independentInstance>):
<div ref="independentInstance" v-show="false">
<template v-pre> <!-- v-pre disable compiling content of template -->
<div> <!-- We need this div, because only one root element allowed in templates -->
<h2>Reusable template</h2>
<input type="text" v-model="testContext.readWriteVar">
<input type="text" v-model="readOnlyVar">
<progress-bar></progress-bar>
</div>
</template>
</div>
Now, you can reuse independentInstance template:
<v-runtime-template
:template="$refs.independentInstance.innerHTML"
v-if="$refs.independentInstance">
</v-runtime-template>
But keep in mind that you cannot modify readOnlyVar from inside independentInstancetemplate - vue will warn you with:
[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: "readOnlyVar"
But you can wrap it in object and it will work:
#Component({
components: {
VRuntimeTemplate
}
})
export default class ObjectList extends Vue {
reusableContext = {
readWriteVar: '...'
};
readOnlyVar = '...';
}
You could try Portal vue written by LinusBorg a core Vue team member.
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
Sample code:
<template>
<div id="parentComponent">
<portal to="independentInstance">
<!-- This slot content will be rendered wherever the <portal-target>
with name 'independentInstance' is located. -->
<independent-component :first-name="firstName" />
</portal>
<some-child-component>
<portal-target name="independentInstance">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
</some-child-component>
</div>
</template>
There is also a vue-simple-portal written by the same author that is smaller but that mounts the component to end of body element.
My answer from #NekitoSP gave me an idea for a solution. I have implemented the sample below. It worked for me. Perhaps you want to use it as a custom component with props.
keywords: #named #template #vue
<template>
<div class="container">
<div ref="templateRef" v-if="false">write here your template content and add v-if for hide in current place</div>
....some other contents goes here
<p v-html="getTemplate('templateRef')"></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
Vue.extend({
methods:{
getTemplate(tempRef){
return this.$refs[tempRef].innerHTML
}
}
})
</script>
X-Templates
Use an x-template. Define a script tag inside the index.html file.
The x-template then can be referenced in multiple components within the template definition as #my-template.
Run the snippet for an example.
See the Vue.js doc more information about x-templates.
Vue.component('my-firstname', {
template: '#my-template',
data() {
return {
label: 'First name'
}
}
});
Vue.component('my-lastname', {
template: '#my-template',
data() {
return {
label: 'Last name'
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-firstname></my-firstname>
<my-lastname></my-lastname>
</div>
<script type="text/x-template" id="my-template">
<div>
<label>{{ label }}</label>
<input />
</div>
</script>
Not really sure i understand your problem here, but i'll try to give you something that i will opt to do if i want to add two components in one template.
HeaderSection.vue
<template>
<div id="header_id" :style="'background:'+my_color">
welcome to my blog
</div>
</template>
<script>
export default {
props: ['my_color']
}
</script>
BodySection.vue
<template>
<div id="body_id">
body section here
</div>
</template>
<script>
export default {
}
</script>
home.vue
<template>
<div id="parentComponent">
<header-section :color="my_color" />
<body-section />
</div>
</template>
<script>
import HeaderSection from "./components/HeaderSection.vue"
import BodySection from "./components/BodySection.vue"
export default {
name: "home",
components: {
HeaderSection,
BodySection
},
data() {
return {
my_color: "red"
}
}
}
</script>

Vue.js add function to a component

I am using Vue.js and I would like to add a function to my component, Map.vue, so when I click the button it calls a function declared in the same file :
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button >Do stuff</button>
</div>
</template>
I have only seen examples when the function is declared in the App.vue. How do you do that in my Map.vue file ?
Events
Both App.vue and Map.vue are components so they work the same for calling functions (methods) "in the same file" (single file components).
Trigger an event by adding v-on:click="yourFunctionHere()" and then make sure to declare your function in the methods object of the script section:
Map.vue
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button v-on:click="doStuff()">Do stuff</button>
</div>
</template>
<script>
export default {
methods: {
doStuff () {
alert('Did something!')
}
}
}
</script>
Custom Events
Since it's a little unclear what confused you about the child component Map.vue (since you understand the parent App.vue), perhaps you are asking how to call a function in App.vue from a button in the child Map.vue?
If that's the case, then you need to use custom events for child to parent communication:
Map.vue
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button v-on:click="doStuff()">Do stuff</button>
</div>
</template>
<script>
export default {
methods: {
doStuff () {
this.$emit('childStuff', 'some value from child')
}
}
}
</script>
App.vue
<template>
<div>
<Map v-on:childStuff="value => { parentStuff(value) }"></Map>
</div>
</template>
<script>
import Map from './components/Map'
export default {
components: {
Map
},
methods: {
parentStuff (value) {
alert(value)
}
}
}
</script>

Dynamically add a child component in Vue JS

I need some help in Vue JS and Laravel with adding a child vue component.
I have a parent component called "wrapper" and some child components called like "show-1", "show-2", "show-3" ... etc.
Parent component:
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<component
is="view"
></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
An exmaple child component like "show-1":
<template>
<div> show-1 </div>
</template>
This code below is in blade for rendering wrapper component with a dynamic child component name:
<wrapper
title="Example"
view="show-1"
></wrapper>
This code is not working but if i change the parent view data "show-1" instead of empty, it works. why ?
When I change the view prop, child vue component should be changed too. How could I do this ?
I want to pass the view attribute to parent component dynamically.
You can use :is attribute. You can read more about it here:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the same mount point and dynamically switch between
multiple components using the reserved element and
dynamically bind to its is attribute....
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<!-- make sure to use : -->
<component v-if="view" :is="view"></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
#Eduardo has the right answer. To add to it, import your components into the parent and switch between them via a data property:
...
<component :is="current"></component >
...
data: {
current: 'show1'
},
components: {
show1: Show1Component,
show2: Show2Component,
show3: Show3Component
}
The key is to bind the component using the name of the dynamic component.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Access component data in other component using slots in VueJS

I am trying to build a VueJS component. I have two other components which I want to use in a parent child manner. Now, I want to pass parent's data to child. It is like this:
App.vue
<template>
<div id="app">
<parent>
<child></child>
<child></child>
<child></child>
<child></child>
</parent>
</div>
</template>
<script>
import child from './child.vue'
import parent from './parent.vue'
var vm = new Vue({
el: '#app',
components: {
parent,
child
}
})
</script>
parent.vue
<template>
<div class="container">
This is parent component
<slot>
</slot>
</div>
</template>
<script>
export default {
name: 'parent',
data () {
return {
parentMsg: {
'key1': 'value1',
'key2': 'value2'
}
}
}
}
</script>
child.vue
<template>
<div class="container">
Child component
<!-- I have to place a div here which will consume parentMsg object -->
<!-- Each instance of child component rendered should have access to parentMsg -->
<div>
<!-- Some HTML to consume the parentMsg -->
</div>
</div>
</template>
<script type="text/javascript">
export default {
name: 'child',
data () {
return {
demo: 'Demo message changed'
}
}
}
</script>
I want parentMsg from parent.vue to be accessible in child.vue. I don't want to expose parentMsg on App.vue. How can I do this?
I placed the div consuming parentMsg inside (and after too) unnamed slot in parent.vue template and thought that with each instance of child.vue, a copy of div will be rendered. It does not work.
You can pass data in slot in parent.vue
<template>
<div class="container">
This is parent component
<slot :parentMsg="parentMsg"></slot>
</div>
</template>
In app.vue access parentMsg through scope and pass as props to child:
<template>
<div id="app">
<parent>
<template scope="defaultSlotScope">
<child :msg="defaultSlotScope.parentMsg"></child>
<child :msg="defaultSlotScope.parentMsg"></child>
<child :msg="defaultSlotScope.parentMsg"></child>
<child :msg="defaultSlotScope.parentMsg"></child>
</template>
</parent>
</div>
</template
>
In child.vue use msg as props:
<template>
<div class="container">
<div>
{{msg}}
</div>
</div>
</template>
<script type="text/javascript">
export default {
name: 'child',
props:['msg'],
data () {
return {
demo: 'Demo message changed'
}
}
}
</script>
Why dont you put the child-component in the parent template and pass the msg from parent to them ? Slots are not intended to be used like this.
App.vue
<template>
<div id="app">
<parent>
</parent>
</div>
</template>
<script>
import parent from './parent.vue'
var vm = new Vue({
el: '#app',
components: {
parent,
child
}
})
</script>
Parent.vue
<template>
<div class="container">
This is parent component
<child :msg="key1"></child>
<child :msg="key1"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
name: 'parent',
data () {
return {
parentMsg: {
'key1': 'value1',
'key2': 'value2'
}
}
}
}
</script>
Child.vue
<template>
<div class="container">
Child component
<div>
{{msg}}
</div>
</div>
</template>
<script type="text/javascript">
export default {
name: 'child',
props: {
msg: String
},
data () {
return {
demo: 'Demo message changed'
}
}
}
</script>

Categories