So I'm using Jade (Pug) to render my templates via Node, and I'm trying to use React to interact with various HTML elements. Is there a special way to do this?
React (favourite-item.js)
var FavouriteItemButton = React.createClass({
getInitialState: function () {
return {favourite: false};
},
handleClick: function (event) {
this.setState({favourite: !this.state.favourite});
},
render: function() {
var text = this.state.favourite ? 'like' : 'haven\t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
RenderDOM.render(
<FavouriteItemButton />,
document.getElementById('example')
);
I've imported React/React DOM and obviously the file above. I have a HTML element on my Jade (index.jade) template:
p#example like
But nothing is changing on click, I presume it's down to the tags or some special way to render React elements via Jade.
My includes:
script(type='text/javascript' src='js/libs/react.min.js')
script(type='text/javascript' src='js/libs/react-dom.js')
script(type='text/babel' src='js/favourite-item.js')
I'm right in thinking I should include the type as text/babel?
Related
I am using the VUE JS code and trying to add the setAttribute to some of the tags.
Here is the code I am using :
changetab() {
const demoClasses = document.querySelectorAll(".delCon__select");
demoClasses.forEach(button => {
button.setAttribute("tabindex", "0");
});
return true;
},
but when I view in the code inspector, It does not show added to it, I have added the above function in computed.
template is like this :
<template>
<el-container class="orders"></el-download>
</template>
You need to make this type of request in Vue's Lifecycles, like: created or mounted.
Something like:
mounted() {
this.changetab()
}
Computed would not be the most appropriate place for this type of action.
I'm trying to test my tooltip component, but it seems it does not exist :cry:
My .html
<div>
<boxComponent>
Some text
<tooltipComponent
#mouseover.native="handleHover(true)"
#mouseleave.native="handleHover(false)"
>This text appears on Hover</tooltipComponent>
</boxComponent>
<switchComponent button-type="button" :label="false" #change="activeFun" />
</div>
My .js
methods: {
handleHover (s) {
this.onHoverTooltip = s
},
}
My .spec.js
const localVue = createLocalVue()
localVue.use(Vuex)
//...
it('should reveal tooltip\'s mesage', () => {
const wrapper = shallowMount(ozFilters, {
propsData: {
//others stuffs,
label: false,
},
localVue,
store,
stubs: ['tooltipComponent', 'boxComponent', 'switchComponent'],
})
expect(wrapper.find('tooltipComponent-stub').exists()).toBeFalsy()
// wrapper.vm.label = true
wrapper.vm.handleHover(true)
expect(wrapper.find('tooltipComponent-stub').exists()).toBeTruthy()
})
I need to understand what should I do to test the tooltip component that is already a custom component.
Even without the -stub it does not work.
The error is occurring in this line expect(wrapper.find('tooltipComponent-stub').exists()).toBeTruthy() with says that the expect is false.
Well, there are a couple of things that need to be fixed/clarified.
First of all, you are using shallowMount to create a component which you want to test, it stubs all custom components provided in tested component so you don't have to additionally use stub parameter to stub them, you can easily delete this: stubs: ['tooltipComponent', 'boxComponent', 'switchComponent'].
When you want to find specific component it's recommended to use findComponent instead of just find. (Using find to search for a component is actually deprecated). To properly identify component it's best to import it in your test file and use it in findComponent argument, something like this:
import BoxComponent from 'path/to/BoxComponent'
it('lovely test', () => {
wrapper.findComponent(BoxComponent)
})
After this your test should pass, but there are some things to consider like using mount instead of shallowMount. It's explained here, but to wrap it up, mount will render actual component with its children, where shallowMount stubs components inside tested component, so you are not testing the actual component but some "shadow" of its true nature. To see a difference between this functions I would recommend to create a wrapper with both functions and then see what html() will return from them.
I have a Datatables.net jquery plugin as a vue component:
DatatablesCGU:
<template>
<table v-bind="$props" ref="tableElement" class="table table-striped table-hover table-bordered">
<slot></slot>
</table>
</template>
<script>
import $ from 'jquery';
// Datatables
require('datatables.net-bs');
require('datatables.net-bs4/js/dataTables.bootstrap4.js');
require('datatables.net-buttons');
require('datatables.net-buttons-bs');
require('datatables.net-responsive');
require('datatables.net-responsive-bs');
require('datatables.net-responsive-bs/css/responsive.bootstrap.css');
require('datatables.net-buttons/js/buttons.colVis.js'); // Column visibility
require('datatables.net-buttons/js/buttons.html5.js'); // HTML 5 file export
require('datatables.net-buttons/js/buttons.flash.js'); // Flash file export
require('datatables.net-buttons/js/buttons.print.js'); // Print view button
require('datatables.net-keytable');
require('datatables.net-keytable-bs/css/keyTable.bootstrap.css');
require('datatables.net-select');
require('jszip/dist/jszip.js');
require('pdfmake/build/pdfmake.js');
require('pdfmake/build/vfs_fonts.js');
//Evita o alert chato do datatables em caso de erro
$.fn.dataTable.ext.errMode = function ( settings, helpPage, message ) {
console.error(message);
};
/**
* Wrapper component for dataTable plugin
* Only DOM child elements, componets are not supported (e.g. <Table>)
*/
export default {
name: 'DatatableCGU',
props: {
/** datatables options object */
options: { type: Function, "default": ()=>{} },
/** callback that receives the datatable instance as param */
dtInstance: Function
},
data(){
return { datatables : null}
},
mounted() {
const dtInstance = $(this.$refs.tableElement).DataTable(this.options());
this.datatables = dtInstance;
if (this.dtInstance) {
this.dtInstance(dtInstance);
}
this.$root.$on('filtrar', this.refresh);
},
destroyed() {
$(this.$refs.tableElement).DataTable({destroy: true});
},
methods: {
refresh(filtros) {
this.datatables.ajax.reload();
}
}
}
</script>
On another component, i use this passing a datatables options with some custom renders on columns properties:
...
methods: {
getOptions(){
let options = this.getDefaultOptions();
options.ajax.url = "/api/auth/usuarios";
options.filtrador = this.filtrador;
options.columns = [
this.colunaDeSelecao(this.modoPopup)
,{name: "cpf", data: "cpf", title: "CPF"}
,{name: "nome", data: "nome", title: "Nome"}
,{name: "email", data: "email", title: "E-mail"}
,{name: "id", data: "id", title: "Ações", visible: !(this.modoPopup), sortable:false, className:"dt-center", width: "200px", render: function(data, type, row) {
return `<span class='btn-group btn-group-sm'>
<button id='btnAlternar__${data}' data-id='${data}' class='btn btn-${row.ativo?"danger":"success"}' data-toggle='tooltip' title='${row.ativo?"Inativar":"Ativar"}'><i class='fas fa-power-off'></i></button>
<a href='${window.$baseURL}auth/usuarios/${data}' class='btn btn-warning' data-toggle='tooltip' title='Editar'><i class='far fa-edit'></i></a>
</span>`;
}}
];
options.initComplete = () =>{
this.getDefaultOptions().initComplete();
this.criarTogglersSituacao();
};
return options;
}
...
If you notice the last column render creates a <a href='${window.$baseURL}auth/usuarios/${data}' ... that obviously isn't a router-link and doesn't trigger vue router properly, causing an undesired page refresh.
I need the link to do a router push instead of a page refresh. How is this possible?
There is no good answer to that problem. datatables is not really compatible with Vue.js. With Vue, the usual way to go would be to pass your reactive HTML structure within a slot to such a library. Because datatables requires you to use a render function and return static HTML as a string, you cannot pass any JavaScript logic along.
The main problem is that you need to pass an event from the link tag to the Vue component. One approach would be to pass HTML in the render function which then can be selected with a specific selector (e.g. adding a class). You also need to add the link data/the item's ID to the HTML element (e.g. with a data-link="" attribute). When datatables has finished rendering, you can add a click listener to all the links. This click listener handler function needs to read the link/ID of the link and pass it to the router. Then, you can use Vue's router.push() function.
When you are implementing a solution with the above approach, make sure to assign and remove the click listeners depending on the lifecycle events of datatables. It might be necessary to add and remove the listeners on each page switch.
I'm trying to test the template of my Vue app after making an ajax request which is changing one variable of the component' data. This variable (books) is use to conditional render the gallery
CONTEXT: I want to create a gallery in order to show the books I have stored in my back end. For this, I fetching my books on mounting the component. The result of this is set in the variable books. What I'm trying to test is that, after the ajax call, the component renders the gallery with the books
PROBLEM: When the books variable is set, the div <div v-else-if='books.length > 0'>SHOW GALLERY</div> should be rendered, but the "else" div (<div v-else class='loader'>Loading</div>) is still rendered
The next two blocks of code are the component and the test itself:
BookGallery.vue (component I'm testing)
<template>
<v-content>
<v-container fluid>
/*** Conditional rendering: After the ajax request books > 0, so this div should be rendered ***/
<div v-if='books.length > 0'>SHOW GALLERY</div>
<div v-else class='loader'>Loading</div>
</v-container>
</v-content>
</template>
<script lang='ts'>
import {Component} from 'vue-property-decorator';
import {MyMixin} from '../mixin';
#Component({components: {BookInformation}})
export default class BookGallery extends MyMixin {
public books: string[] = [];
public async mounted() {
/*** books is set as the result of the ajax request ***/
this.books = (await this.$http.get(this.host + '/books')).data;
}
}
</script>
<style scoped lang='scss'></style>
TEST
#test
public async 'after calling the books, the gallery show all of them'() {
/*** MOCKING THE RESPONSE ***/
TestCase.OK_200({
books: [
{ uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
{ uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
],
});
/*** MOUNTING MY COMPONENT ***/
const wrapper = TestCase.shallowMount(BookGallery);
/** ASSERTING **/
await flushPromises().then(() => {
/** the first "expect" passes, so books > 0 = true **/
expect(wrapper.vm.$data.books).to.eqls({
books: [
{ uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
{ uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
],
});
/** This is failing. The application should read 'SHOW GALLERY' when books > 0 (something tested in the previous assert), as explained in the first comment of the component's template, but is not updating the dom, only the data **/
see('SHOW GALLERY');
});
}
The QUIESTION: How can I update my DOM for my very last assert -see("SHOW GALLERY")-?
UPDATE
see Function
The function only searches for a HTML element in the wrapper that vue-test-utils is using for mounting the application. In this case, as I have left it null, it is searching the text "SHOW GALLERY" over the whole HTML file
export const see = (text: string, selector?: string) => {
const wrap = selector ? wrapper.find(selector) : wrapper;
expect(wrap.html()).contains(text);
};
I just solved a similar problem, using shallowMount and its sync: false option.
This deactivates sync rendering, requiring you to give the renderer some time to perform its job (await wrapper.vm.$nextTick()) where needed.
Once this done, the component was re-rendered based on my reactive data, as expected.
I ran into very similar problem and solved it with await wrapper.vm.$forceUpdate(); before assertion that failed. This forces Vue to update view.
Initially Vue Test Utils run updates synchronously. But later they removed sync mode. Here they explain reasons and show how to write tests:
Test code will change from this:
it('render text', (done) => {
const wrapper = mount(TestComponent)
wrapper.trigger('click')
wrapper.text().toContain('some text')
})
To this:
it('render text', async () => {
const wrapper = mount(TestComponent)
wrapper.trigger('click')
await Vue.nextTick()
wrapper.text().toContain('some text')
})
So, to fix your issue you need to add await wrapper.vm.$nextTick() before assertion see('SHOW GALLERY');
I had a similar problem where child components markup was not rendered on mount and based on the other answers the following worked for me:
test('it works as expected', async () => {
const wrapper = await mount(TestComponent);
//expect(...)
});
I am trying to create a generic HTML Window from Vue application.
What I want
I click "edit" on item from list, a DHTML popup will be opened with a Vue component inside. I can click Save or close the opened component need to removed from the page and may the caller recive a event/promise with resolve (Save) or reject (Close).
What I am tried
I was started via DOM:
function Win(vueapp,obj) {
var div = document.createElement("div");
var Win = JQueryOldFunction()
//need a way to resolve when the user hit 'Save' and reject when closing
var resolve,reject, promise = new Promise(function(a, b) {
resolve=a, reject=b;
});
new Vue({
el: div,
data: {data:obj} //need a way to pass it to the component
render: function (h) {
return h("m");
},
components: {
'm': httpVueLoader(vueapp+'.vue') // this from https://github.com/FranckFreiburger/http-vue-loader
}
});
return promise;
}
This is working, but I can't use Vue to destroy the window after clicking save, and can't pass objects inside
so I was tried other option:
<component :is="gui.window" transition="fade" transition-mode="out-in"></component>
Model.gui.window='EditGroup'
(took from here: https://coligo.io/dynamic-components-in-vuejs/ )
This is better, it working, does not allowing more than one window, but the internal component can't destroy itself.