I am running a Vue 3.0 with Quasar library. In the application there is a component that has a child component in it. With vue test utils v 2.2.6, I cannot seem to locate the child components in order to generate events. Why?
Simplified Parent Component
<template>
<q-dialog square persistent transition-show="scale" transition-hide="scale" >
<q-card-section class="q-px-xs q-pt-none">
<ChildComponent/>
</q-card-section>
</q-dialog>
</template>
<script setup>
//imports, props, etc
</script>
<style> ... </style>
Simplified Child Component
<template>
<q-card square bordered id="new-recipient-root-element" class="new-recipient">
<q-form>...</q-form>
</q-card>
</template>
<script setup>
//imports, props, etc
</script>
<style> ... </style>
Using Jest / vue test utils like so:
wrapper = mount(ParentComponent, {
props: { ...},
global: {
plugins: [i18n],
provide: {
store: ...
},
mocks: { ... }
}
});
wrapper.vm.show();
await nextTick();
domWrapper = new DOMWrapper(document.body);
...
await domWrapper.findComponent(ChildComponent).vm.$emit('myEvent');
domWrapper.findComponent(ChildComponent) always returns nothing. If I try domWrapper.findAllComponents(), it returns an empty array.
Also noticed that I can locate all the elements, but if I click on a button, no event seems to be generated since the handlers are not called and wrapper.emitted() contains nothing.
What am I missing?
Related
Does lit-html have by any change something like React's ref feature?
For example in the following pseudo-code inputRef would be a callback function or an object { current: ... } where lit-html could pass/set the HTMLElement instance of the input element when the input element is created/attached.
// that #ref pseudo-attribute is fictional
html`<div><input #ref={inputRef}/></div>`
Thanks.
In lit-element you can use #query property decorator. It's just syntactic sugar around this.renderRoot.querySelector().
import { LitElement, html, query } from 'lit-element';
class MyElement extends LitElement {
#query('#first')
first;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
lit-html renders directly to the dom so you don't really need refs like you do in react, you can use querySelector to get a reference to the rendered input
Here's some sample code if you were only using lit-html
<html>
<head>
<title>lit-html example</title>
<script type="module">
import { render, html } from 'https://cdn.pika.dev/lit-html/^1.1.2';
const app = document.querySelector('.app');
const inputTemplate = label => {
return html `<label>${label}<input value="rendered input content"></label>`;
};
// rendering the template
render(inputTemplate('Some Label'), app);
// after the render we can access it normally
console.log(app.querySelector('input').value);
</script>
</head>
<body>
<div class="app"></div>
<label>
Other random input
<input value="this is not the value">
</label>
</body>
</html>
If you're using LitElement you could access to the inner elements using this.shadowRoot.querySelector if you're using shadow dom or this.querySelector if you aren't
As #WeiChing has mentioned somewhere above, since Lit version 2.0 you can use the newly added directive ref for that:
https://lit.dev/docs/templates/directives/#ref
-- [EDIT - 6th October 2021] ----------------------------
Since lit 2.0.0 has been released my answer below
is completely obsolete and unnecessary!
Please check https://lit.dev/docs/api/directives/#ref
for the right solution.
---------------------------------------------------------
Even if this is not exactly what I have asked for:
Depending on the concrete use case, one option to consider is the use of directives.
In my very special use-case it was for example (with a little luck and a some tricks) possible to simulate more or less that ref object behavior.
const inputRef = useElementRef() // { current: null, bind: (special-lit-html-directive) }
...
return html`<div><input ref=${inputRef.bind}/></div>`
In my use case I could do the following:
Before rendering, set elementRef.current to null
Make sure that elementRef.current cannot be read while the component is rerendered (elementRef.current is not needed while rendering and an exception will be thrown if someone tries to read it in render phase)
That elementRef.bind directive will fill elementRef.current with the actual DOM element if available.
After that, elementRef.current can be read again.
For lit-html v1, you can define your own custom Derivative:
import { html, render, directive } from "lit-html";
function createRef(){ return {value: null}; }
const ref = directive((refObj) => (attributePart) => {
refObj.value = attributePart.committer.element;
});
const inputRef = createRef();
render(html`<input ref=${ref(inputRef)} />`;
// inputRef.value is a reference to rendered HTMLInputElement
I am new to vue.js and currently I am building an app for learning purposes.
What I want to do:
I have a parent component which has a bunch of buttons with different id's.
The child component will wait for those id's to be sent by the parent and it will decide what to display based on the id. Thats all.
I wont post the full code because it's too large but I have tried a bunch of stuff like props and state but honestly it is so confusing.
I come from React background and I am still confused.
Parent component
<template>
<button id="btn1">Show banana</button>
<button id="btn2">Show orange</button>
</template>
<script>
export default {
name: 'Parent',
data: function {
//something
},
props: {
// ?????
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
data: function() {
something: ''
if(id from parent === id I want) {
something = object.withpropIneed
}
},
};
</script>
You need to map the data from parent and pass it to child, thats it!
In example i make passing a html string and binding that html received through 'fromParentHtml' prop mapped on child, so inside child component 'this.fromParentHtml' pass to exists because it is defined in props and every time you click in parent button executes the 'show' function and change the value from passed prop to child through parent 'html' data .. =)
<template>
<div>
Current html sent to child '{{html}}'
<br>
<button #click="show('banana')">Banana</button>
<button #click="show('orange')">Orange</button>
<button #click="show('apple')">Apple</button>
<!-- Create child component -->
<child-component :fromParentHtml="html"></child-component>
</div>
</template>
<script>
export default {
name: "test3",
components: {
'child-component': {
template: "<div>Child component... <br> <span v-html='fromParentHtml'></span> </div>",
//Child component map a prop to receive the sent html from parent through the attribute :fromParentHtml ...
props: {
fromParentHtml: {
required: true,
type: String
}
}
}
},
data(){
return {
html: ''
}
},
methods: {
show(fruit){
this.html = '<span>The fruit is ' + fruit + ' !</span>';
}
}
}
</script>
<style scoped>
</style>
If helped you please mark as correct answer! Hope it helps.
Edit 1:
Assuming you have webpack to work with single file components, to import another component just do:
<template>
<div>
<my-child-component></my-child-component>
</div>
</template>
<script>
//Import some component from a .vue file
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
//And pass it to your component components data, identified by 'my-child-component' in the template tag, just it.
'my-child-component': ChildComponent
},
data(){
},
methods: {
}
}
</script>
Just for the sake of it, I think you were looking for this:
<template>
<button id="btn1" #click = "id = 1">Show banana</button>
<button id="btn2" #click = "id = 2">Show orange</button>
<child-component :childid = "id"></child-component>
</template>
<script>
import childComponent from 'childComponent'
export default {
name: 'Parent',
data () {
return {
id: 0
}
},
components: {
childComponent
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
props: {
childid: String
},
data: function() {
something: ''
if(this.childid === whatever) {
something = object.withpropIneed
}
},
};
</script>
Solved my problem by taking a different approach.
I have implemented state and my component behaves exactly as I wanted to.
I found this link to be helpful for me and solved my problem.
Thank you.
I seem to do something wrong when I'm trying to target child components in nested router-views with click events.
Current situation:
I have a component one and component two. Both have a child component called dialog.
Component one and two are being loaded through a router-view in parent component dashboard. Each view has a button to show their child component "Modal".
The button seems to work fine on the view that gets loaded on pageload. But as soon as I switch routes the showModal function does not know the dialog element from which view to target.
I thought the components would be destroyed and rebuilt upon switching routes but apparently not.
Here is my code, I hope someone is able to help:
App
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
/**
* First we will load all of this project's JavaScript dependencies which
* include Vue and Vue Resource. This gives a great starting point for
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap')
/**
* Next, we will create a fresh Vue application instance and attach it to
* the body of the page. From here, you may begin adding components to
* the application, or feel free to tweak this setup for your needs.
*/
Vue.component('vuetest', require('./components/vuetest.vue'))
const Dashboard = require('./components/dashboard.vue')
const FirstRoute = require('./components/firstroute.vue')
const Second = require('./components/secondroute.vue')
const routes = [
{
path: '/dashboard',
component: Dashboard,
children: [
{
path: 'firstview',
name: 'firstview',
canReuse: false,
component: FirstRoute
},
{
path: 'secondview',
name: 'secondview',
canReuse: false,
component: Second
}
]
}
]
const router = new VueRouter({
routes // short for routes: routes
})
window.EventHub = new Vue()
const app = new Vue({
el: '#app',
router
});
Vuetest
<template>
<div>
<h1>Vue Test</h1>
<router-view></router-view>
</div>
</template>
<script>
export default {
created() {
},
mounted() {
console.log('Component ready.')
}
}
</script>
Dashboard Route
<template>
<div>
<h1>Dashboard</h1>
<navigation></navigation>
<router-view></router-view>
</div>
</template>
<script>
Vue.component('navigation', require('./navigation.vue'))
</script>
Navigation
<template>
<div>
<router-link :to="{ name: 'firstview' }">first</router-link>
<router-link :to="{ name: 'secondview' }">second</router-link>
</div>
</template>
First Route
<template>
<div class="firstroute">
<h1>First Route</h1>
<button class="showmodal" v-on:click="showModal">Showmodal</button>
<modal></modal>
</div>
</template>
<script>
export default {
methods: {
showModal: function () {
EventHub.$emit('showModal')
}
}
}
Vue.component('modal', require('./modal.vue'));
</script>
Second Route
<template>
<div class="secondroute">
<h1>Second Route</h1>
<button class="showmodal" v-on:click="showModal">Showmodal</button>
<modal></modal>
</div>
</template>
<script>
export default {
methods: {
showModal: function () {
EventHub.$emit('showModal')
}
}
}
Vue.component('modal', require('./modal.vue'));
</script>
Modal
<template>
<div class="dialog hidden">
Dialog
</div>
</template>
<style>
.hidden {
display: none;
}
</style>
<script>
export default{
created() {
EventHub.$on('showModal', this.showModal);
},
methods: {
showModal: function() {
document.querySelector('.dialog').classList.toggle('hidden');
}
}
}
</script>
I really appreciate any help.
tiny recomendations
':class' directive instead of native code:
document.querySelector('.dialog').classList.toggle('hidden');
components:
import Modal from './modal'
export default {
...
components:
Modal
}
...
}
instead of
Vue.component('modal', require('./modal.vue'));
.. also Vuex is a good point for this case
additional:
https://github.com/vuejs/vue-devtools
https://jsfiddle.net/uLaj738k/2/
As it turns out the problem was the moment I called the querySelector method.
Assigning the .dialog element to a const in mounted() solved my problem.
I want to render the pure HTML coming from some external source into react component. I saw few solutions where people are talking about some conversion tools (HTML to JSX) but I want to handle everything in my component so while mounting it will get the HTML response and that needs to render.
You can use dangerouslySetInnerHTML for this:
function createMarkup() { return {__html: 'First · Second'}; };
<div dangerouslySetInnerHTML={createMarkup()} />
But as the method name suggests: you should be very sure of what you are doing there and the security implications it has.
This shouldn't be difficult to do . Assign your HTML to a div and then render it using {variable name} JSX allows you to do this and with ES6 integration you can also use class instead of className.
var Hello = React.createClass({
render: function() {
var htmlDiv = <div>How are you</div>
return <div>Hello {this.props.name}
{htmlDiv}
</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I’m working on an Angular 2 app and I have a component which inclundes a simple SVG rectangle, I’m trying to use Hammer.js library to be able to deplace and reform this SVG rectangle inside the view of my component,
therefore I’ve done those steps:
I’ve downloaded and copied 3 files to my project repository
hammer.js
hammer.min.js
hammer.min.map
I’ve added this script tag to my index head:
<script src="dev/jqueryLibs/hammer/hammer.js" type="text/javascript"></script>
And I get this error in the console:
Uncaught TypeError: Cannot read property 'addEventListener' of null
I’ve tried to import it in my component and add reference betwen the methode and the svg element, like this:
TS.File contents:
import {Component, ElementRef, AfterViewInit } from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {FORM_DIRECTIVES} from "angular2/common";
#Component({
selector: 'content',
templateUrl: 'content.component.html',
styleUrls: ['content.component.css'],
directives: [FORM_DIRECTIVES],
})
export class ContentComponent implements AfterViewInit{
static hammerInitialized = false;
constructor(private el:ElementRef)
{}
ngAfterViewInit() {
console.log('in ngAfterViewInit');
if (!ContentComponent.hammerInitialized) {
console.log('hammer not initialised');
var myElement = document.getElementById('test1');
var hammertime = new Hammer(myElement);
hammertime.on('swiperight', function(ev) {
console.log('caught swipe right');
console.log(ev);
});
ContentComponent.hammerInitialized = true;
} else {
console.log('hammer already initialised');
}
}
View file contents:
<svg class="simulation">
<rect id="test1" x="20" y="500" height="150" width="200" style="stroke:#EC9A20;stroke-width: 15; fill: none"/>
</svg>
Therefore I still am not able to move my SVG rectangle and it seems that Hammer.js is not even running according to this console message:
hammer not initialised
Anybody can tell me where is the error or what should I do?
This was resolved: The script tag of hammer declaration
must be placed after the jquery-ui library declaration in HTML file.
<script src="./dev/resources/js/jquery.js"></script>
<script src="./dev/resources/js/jquery-ui.js"></script>
<script src="./dev/jqueryLibs/jquery-1.12.1.min.js"></script>
<script src="./dev/jqueryLibs/jquery-migrate-1.2.1.min.js"></script>
<!--importer ici la js de bootstrap-->
<script src="node_modules/bootstrap/dist/js/bootstrap.js"></script>
<!--Importer ici hammer.js-->
<script src="dev/jqueryLibs/hammer/hammer.js" type="text/javascript"></script>