I use vue js and I display a graph with chartjs. When I click on the graph I want emit an event for get data in parent component. My onClick function works but not the event.
Do you have an idea of my problem ?
Component Line2.vue
<script>
import { mixins, Line } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
extends: Line,
mixins: [reactiveProp],
props: ["options"],
mounted() {
const self = this;
console.log(self);
this.options.onClick = function (e, tooltipItems) {
console.log(tooltipItems[0].__ob__); //.logged
self.$emit("sendIndex", tooltipItems[0]._index);
};
this.renderChart(this.chartData, this.options);
},
};
</script>
<style scoped></style>
My main component
...
<h1 v-on:sendIndex="getIndexCoord($event)">{{ indexCoord }}</h1>
...
methods: {
getIndexCoord: function (e) {
console.log("rrr", e); //Not logged
this.indexCoord = e;
},
}
Regards
1.first you create EventBus.js file
import Vue from 'vue';
export const EventBus = new Vue();
2.In your char.js file code like below
import { EventBus } from "../EventBus.js";
import { mixins, Line } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
extends: Line,
mixins: [reactiveProp],
props: ["options"],
mounted() {
const self = this;
console.log(self);
this.options.onClick = function (e, tooltipItems) {
console.log(tooltipItems[0].__ob__); //.logged
EventBus.$emit("sendIndex", tooltipItems[0]._index);
};
this.renderChart(this.chartData, this.options);
},
};
where you want to access your data in that file like below
import { EventBus } from "../EventBus.js";
mounted() {
EventBus.$on('sendIndex', data => {
console.log(data)
});
},
Related
Using vue-test-utils to test the component using pinia, I need to modify the value of the state stored in pinia, but I have tried many methods to no avail. The original component and store files are as follows.
// HelloWorld.vue
<template>
<h1>{{ title }}</h1>
</template>
<script>
import { useTestStore } from "#/stores/test";
import { mapState } from "pinia";
export default {
name: "HelloWorld",
computed: {
...mapState(useTestStore, ["title"]),
},
};
</script>
// #/stores/test.js
import { defineStore } from "pinia";
export const useTestStore = defineStore("test", {
state: () => {
return { title: "hhhhh" };
},
});
The following methods have been tried.
Import the store used within the component to the test code and make changes directly, but the changes cannot affect the component.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
import { useTestStore } from "#/stores/test";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia()],
},
});
const store = useTestStore();
store.title = "xxxxx";
console.log(wrapper.text()) //"hhhhh";
});
Using the initialState in an attempt to overwrite the contents of the original store, but again without any effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia({ initialState: { title: "xxxxx" } })],
},
});
console.log(wrapper.text()) //"hhhhh";
});
Modify the TestingPinia object passed to global.plugins in the test code, but again has no effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const pinia = createTestingPinia();
pinia.state.value.title = "xxxxx";
const wrapper = mount(HelloWorld, {
global: {
plugins: [pinia],
},
});
console.log(wrapper.text()) //"hhhhh";
});
Use global.mocks to mock the states used in the component, but this only works for the states passed in with setup() in the component, while the ones passed in with mapState() have no effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia()],
mocks: { title: "xxxxx" },
},
});
console.log(wrapper.text()) //"hhhhh"
});
This has been resolved using jest.mock().
import { mount } from "#vue/test-utils";
import { createPinia } from "pinia";
import HelloWorld from "#/components/HelloWorld.vue";
jest.mock("#/stores/test", () => {
const { defineStore } = require("pinia");
const useTestStore = defineStore("test", { state: () => ({ title: "xxxxx" }) });
return { useTestStore };
});
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: { plugins: [createPinia()] },
});
expect(wrapper.text()).toBe("xxxxx");
});
Thanks to Red Panda for this topic. I use "testing-library", and "vue-testing-library" instead of "vue-test-utils" and "jest", but the problem is the same - couldn't change pinia initial data of the store.
I finally found a solution for this issue without mocking the function.
When you $patch data, you just need to await for it. Somehow it helps. My code looks like this and it totally works:
Popup.test.js
import { render, screen } from '#testing-library/vue'
import { createTestingPinia } from '#pinia/testing'
import { popup } from '#/store1/popup/index'
import Popup from '../../components/Popup/index.vue'
describe('Popup component', () => {
test('displays popup with group component', async () => {
render(Popup, {
global: { plugins: [createTestingPinia()] }
})
const store = popup()
await store.$patch({ popupData: 'new name' })
screen.debug()
})
})
OR you can set initialState using this scheme:
import { render, screen } from '#testing-library/vue'
import { createTestingPinia } from '#pinia/testing'
import { popup } from '#/store1/popup/index'
import Popup from '../../components/Popup/index.vue'
test('displays popup with no inner component', async () => {
const { getByTestId } = render(Popup, {
global: {
plugins: [
createTestingPinia({
initialState: {
popup: {
popupData: 'new name'
}
}
})
]
}
})
const store = popup()
screen.debug()
})
Where popup in initialState - is the imported pinia store from #/store1/popup. You can specify any of them there the same way.
Popup.vue
<script>
import { defineAsyncComponent, markRaw } from 'vue'
import { mapState, mapActions } from 'pinia'
import { popup } from '#/store1/popup/index'
export default {
data () {
return {}
},
computed: {
...mapState(popup, ['popupData'])
},
....
I'm working on a project using Vue 3 with composition API styling.
Composition API is used for both components and defining my store.
Here is my store
player.js
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
export const usePlayerStore = defineStore('player',()=>{
const isMainBtnGameClicked = ref(false)
return { isMainBtnGameClicked }
})
MyComponent.vue
//import { usePlayerStore } from '...'
const playerStore = usePlayerStore()
playerStore.isMainBtnGameClicked = true
isMainBtnGameClicked from my store is updated properly.
You can also update variables from components by passing them by reference to the pinia store. It's working in my project.
For sake of saving future me many hours of trouble, there is a non-obvious thing in play here - the event loop. Vue reactivity relies on the event loop running to trigger the cascade of state changes.
When you mount/shallowMount/render a component with vue-test-utils, there is no event loop running automatically. You have to trigger it manually for the reactivity to fire, e.g.
await component.vm.$nextTick;
If you don't want to mess around with ticks, you have to mock the store state/getters/etc. (which the docs strongly lean toward, without explaining the necessity). Here OP mocked the whole store.
See also: Vue-test-utils: using $nextTick multiple times in a single test
I have been trying to figure this out for some time already, but I can not make it work! I found some examples on internet, but nothing solves it, every time I run my test I get:
Expected number of calls: 1
Received number of calls: 0
> 186 | expect(wrapper.vm.EventBus.$on).toHaveBeenCalledTimes(1);
The component looks like this:
import {EventBus} from 'eventbus'
export default{
data(){ return {}},
mounted(){
EventBus.$on('saveTerminal', function(){
this.someOtherFunction()
})
}
}
Event Bus file looks like this
import Vue from 'vue';
export const EventBus = new Vue();
The Test looks like this:
const GlobalPlugins = {
install(v) {
v.prototype.EventBus = new Vue();
},
};
//Vue.prototype.EventBus = new Vue(); <-- I tried this also, didn't do anything
// Mounting component
const $t = () => {}
const params = { localVue, store, router,
propsData: {
isEdit: false
},
data(){
return {
loading: false,
tabIndex: 1
};
},
mocks:{
$t,
EventBus: {
$on: jest.fn(),
$off: jest.fn(),
$emit: jest.fn()
}
},
}
const wrapper = shallowMount(MyComponent, params)
describe('My component', () => {
it('Event bus', () => {
wrapper.vm.EventBus.$emit('saveTerminal');
expect(wrapper.vm.EventBus.$on).toHaveBeenCalledTimes(1);
expect(wrapper.vm.EventBus.$on).toHaveBeenCalledWith('saveTerminal', expect.any(Function))
});
})
You can use jest.mock() to mock the EventBus module. Your test would require() the module to access the mock, and then verify its $on was called:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
jest.mock('#/utils/EventBus')
describe('MyComponent.vue', () => {
it(`listens to 'saveTerminal' upon mounting`, async () => {
const { EventBus } = require('#/utils/EventBus')
shallowMount(MyComponent)
expect(EventBus.$on).toHaveBeenCalledWith('saveTerminal', expect.any(Function))
})
})
demo
Consider the following simple example using the composition API in Vue 3. I'm trying to have an instance of test available in the functions of my component.
<script>
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
name: 'Test',
setup(){
let test = ref()
onMounted(() => {
doSomething()
})
return{
test,
doSomething
}
}
})
function doSomething(){
console.log(test) //<-- undefined
console.log(this.test) //<-- undefined
}
</script>
How do I access test inside doSomething()? My understanding is that anything returned by setup() should be available throughout the component much like a data() attributes from the options API.
you have to pass the ref as a parameter
<script>
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
name: 'Test',
setup () {
let test = ref(null)
onMounted(() => {
doSomething(test.value)
})
return {
test,
doSomething
}
}
})
function doSomething (param) {
console.log(param); // null
}
</script>
another approach:
// functions.js
import { ref } from 'vue'
export let test = ref(null)
// vue-file
<script>
import { defineComponent, ref, onMounted } from 'vue'
import { test } from '../utils/functions.js'
export default defineComponent({
name: 'Test',
setup () {
onMounted(() => {
doSomething(test)
})
return {
test,
doSomething
}
}
})
function doSomething (param) {
console.log(test.value); // <-- instant access
console.log(param.value); // <-- import via parameter
}
</script>
Here my code :
A random component :
watch: {
'notifications' : {
handler(newVal) {
let questionnaireTypes = ['TwoDaysConnected', 'OneWeekConnected', 'TwoWeekInactive']
if(newVal) {
this.checkDisplayQuestionnaire(questionnaireTypes)
}
},
immediate: false
}
},
The method in mixin_common.js :
import EventBus from "./event-bus.js"
export default window.mixin_common = {
methods: {
checkDisplayQuestionnaire(questionnaireTypes) {
let stateNotifications = this.$store.state.notifications
EventBus.$emit('openQuestionnaireExperimentationModalWithDatas',
stateNotifications)
}
}
The component where is the modal i want to open with the EventBus :
import EventBus from "./event-bus.js"
methods: {
openModal() {
this.$bvModal.show('questionnaireExperimentationModal')
},
},
mounted() {
EventBus.$on('openQuestionnaireExperimentationModalWithDatas', (notifications) => {
this.notifications = notifications
this.openModal()
})
Event-bus.js :
import Vue from 'vue'
const EventBus = new Vue();
export default EventBus;
Actually, the modal does not open, the only way it works is when i place the EventBus.$emit('openQuestionnaireExperimentationModalWithDatas', stateNotifications) directly in the random component, without using the mixin, but i need to open it with the mixin if it's possible.
Anyone have an idea of how to do it?
I've recently learned how to code in React and how to structure the code using Flux. Unfortunately Firebase doesn't play to well with Flux and I need to set up a quick and easy back-end up for a prototype. Some suggest to forgo Flux altogether and to just use Firebase but I'm not sure if Flux will be almost necessary down the road when I hook up a real backend. If it is necessary, should I just force fit React into flux for now and unplug it later, or are there better alternatives to Flux out that I should be taking advantage of? Forgive the noob nature of this question. :)
Here is the basic reflux pattern I use starting with app.js;
import React from 'react';
import AppCtrl from './components/app.ctrl.js';
import Actions from './flux/Actions';
import ApiStore from './flux/Api.Store';
window.React = React;
Actions.apiInit();
React.render( <AppCtrl />, document.getElementById('react') );
app.ctrl.js
import React, {Component} from 'react';
import BasicStore from './../flux/Basic.Store';
var AppCtrlSty = {
height: '100%',
padding: '0 10px 0 0'
}
class AppCtrlRender extends Component {
binder(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); }
render() {
var data = this.state.Data;
data = JSON.stringify(data, null, 2);
var data2 = this.state.Data2;
data2 = JSON.stringify(data2, null, 2);
var data3 = this.state.Data3;
data3 = JSON.stringify(data3, null, 2);
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
React 1.3 ReFlux with WebSocket<br/><br/>
{data}<br/><br/>
Data2: {data2}<br/><br/>
Data3: {data3}<br/><br/>
</div>
);
}
}
function getState() {
return {
Data: BasicStore.getData(),
Data2: BasicStore.getData2(),
Data3: BasicStore.getData3()
};
};
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
this.binder('storeDidChange');
}
componentDidMount() { this.unsubscribe = BasicStore.listen(this.storeDidChange); }
componentWillUnmount() { this.unsubscribe(); }
storeDidChange() { this.setState(getState()); }
}
Actions.js
import Reflux from 'reflux';
var apiActions = [
'apiInit',
'apiInitDone',
'apiSetData'
]
var wsActions = [
'gotData',
'gotData2'
]
var actionArray = wsActions.concat(apiActions);
module.exports = Reflux.createActions(actionArray);
Api.Store.js
import Reflux from 'reflux';
import Actions from './Actions';
import ApiFct from './../utils/ws.api.js';
function _apiInit() { ApiFct.init(); }
function _apiInitDone() { ApiFct.getData(); }
function _apiSetData(data) { ApiFct.setData(data); }
var ApiStoreObject = {
listenables: Actions,
apiInit: _apiInit,
apiInitDone: _apiInitDone,
apiSetData: _apiSetData
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
ws.api.js. This is where you talk to firebase on the server. When you get data from the server just trigger the action to send the data to the store.
import Actions from '../flux/Actions';
module.exports = {
socket: {},
init: function() {
this.socket = new Primus();
this.socket.on('server:GotData', this.gotData);
Actions.apiInitDone();
},
getData: function() { this.socket.send('client:GetData', {}); },
gotData: function(data) { Actions.gotData(data); Actions.gotData2(data); },
setData: function(data) { this.socket.send('client:SetData', data); },
};
Basic.Store.js
import Reflux from 'reflux';
import Actions from './Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';
var _data = {};
function _gotData(data) { _data = data; BasicStore.trigger(); }
function _addonTrigger() { BasicStore.trigger(); }
function BasicStoreInit() { this.listenTo(AddonStore, this.onAddonTrigger); }
var BasicStoreObject = {
init: BasicStoreInit,
listenables: Actions,
mixins: [MixinStoreObject],
onGotData: _gotData,
onAddonTrigger: _addonTrigger,
getData: function() { return _data; },
getData2: function() { return AddonStore.data2; },
getData3: function() { return this.data3; }
}
const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;
The complete pattern is at https://github.com/calitek/ReactPatterns under React.13/ReFluxWebSocket.