I would like to use the vue-chartkick plugin, but want to register it within my single-file components rather than using it globally. Is there a way to achieve the same as
Vue.use(VueChartkick, { Chartkick })
in a single-file component? I've tried to import the plugin and then registered it as a component, but it keeps on telling me that the according component was not defined. This is my single-file component:
<template lang="pug" >
div
area-chart(:data="monthlyRevenue")
</template>
<script>
import Api from '../../../api';
import Chartkick from 'chartkick';
import VueChartkick from 'vue-chartkick'
import Chart from 'chart.js';
export default {
name: 'reporting',
components: {
'area-chart': AreaChart
},
data() {
return {
monthlyRevenue: {}
}
},
created() {
Api.get(window.location.pathname)
.then((response) => {
this.monthlyRevenue = response.body;
})
.catch((response) => {
this.handleErrors(response.body);
});
}
}
</script>
You would need to create a new Vue object and declare it as a component in your single file component. I'm not too familiar with VueChartKick. But this might work.
<template lang="pug" >
div
area-chart(:data="monthlyRevenue")
</template>
<script>
import Vue from 'vue';
import Api from '../../../api';
import Chartkick from 'chartkick';
import VueChartkick from 'vue-chartkick'
import Chart from 'chart.js';
// attach your other plugins in here as required
Vue.use(VueChartkick, {Chartkick});
const newCustomComponent = new Vue();
export default {
name: 'reporting',
components: {
'area-chart': AreaChart,
newCustomComponent: newCustomComponent,
},
data() {
return {
monthlyRevenue: {}
}
},
created() {
Api.get(window.location.pathname)
.then((response) => {
this.monthlyRevenue = response.body;
})
.catch((response) => {
this.handleErrors(response.body);
});
}
}
</script>
In you main.js file add the plugin initialisation:
import Chartkick from 'chartkick'
import VueChartkick from 'vue-chartkick'
import Chart from 'chart.js'
Vue.use(VueChartkick, { Chartkick })
Related
I'm trying to create a webapp using VUE Vite with a router and store. The getter function in the vue file works fine. I have access to the chatMessages stored in the store.js file.
My problem is that I need to call the addMessage Action from the store.js file in the dev console using the browser.
Question: How could I archive this?
On older vue versions it would be done the following way using the main.js file:
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './registerServiceWorker';
import { mapGetters, mapMutations, mapActions } from 'vuex';
Vue.config.productionTip = false;
const app = new Vue({
router,
store,
render: function (h) { return h(App) },
methods: {
...mapMutations([
'showLoading',
]),
...mapActions([
'addNotification',
]),
},
}).$mount('#app');
export default app;
Current vue3 chat.vue file:
<template>
<div></div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'Chat',
data: function() {
return {
}
},
methods: {
},
computed: {
...mapGetters({
chatMessages: 'chatMessageList',
}),
}
}
</script>
Current vue3 store.js file:
import { createStore } from 'vuex'
export default createStore({
state: {
chatMessages: {
list: [
{ type: "a", message: "test" }
]
}
},
mutations: {
addMessage(state, { type, message }) {
state.chatMessages.list.push({ type: type, message: message });
}
},
actions: {
addMessage({ commit }, { type, message }) {
commit('addMessage', { type, message });
}
},
getters: {
chatMessageList(state, getters) {
return state.chatMessages.list;
}
}
})
Current vue3 main.js file:
import App from "./App.vue";
import {createApp} from "vue";
import router from "./router/index.js";
import store from "./store/store";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.js";
window.app = createApp(App).use(router).use(store).mount('#app');
EDIT: I tested it the following way and I can call app.addMessage from the dev console but now the router wont work.
import App from "./App.vue";
import {createApp} from "vue";
import router from "./router/index.js";
import store from "./store/store";
import { mapGetters, mapMutations, mapActions } from 'vuex';
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.js";
window.app = createApp({
methods: {
...mapActions([
'addMessage',
]),
}
}).use(router).use(store).mount('#app');
Assigning the store to the window object seems like a great approach, where you can easily then call it like in domiatorov's answer.
Another approach is:
var store = Array.from(document.querySelectorAll('*')).find(e => e.__vue_app__).__vue_app__.config.globalProperties.$store;
var actions = store._actions;
actions.addMessage[0]('mytype', 'mymessage');
The first part queries the body for an element containing __vue_app__ and will return your instance. In there, you can access config.globalProperties.$store to return your store object.
store is already available in this scope and can be exposed the same way as app:
window.store = store;
It can be used in console the same way as in an app:
store.dispatch(...)
I believe you can simply assign the store to the window object.
But do it from a top level single file component, the very first that is using the store and not in the setup to make sure the store got everything loaded.
Providing you have something like App.vue:
In setup() you would assign:
window.vueStore = store;
and use it from the console calling window.vueStore.
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
In one of our project i try to use vuejs-countdown-timer component, but i get this error
Unknown custom element: - did you register the component
correctly? For recursive components, make sure to provide the "name"
option.
in this package documentation we have:
//Installation
npm i vuejs-countdown-timer -S Import component
// global register at main.js import VueCountdownTimer from
'vuejs-countdown-timer';
Vue.use(VueCountdownTimer);
and basic usage of that:
<template>
<vue-countdown-timer
#start_callback="startCallBack('event started')"
#end_callback="endCallBack('event ended')"
:start-time="'2018-10-10 00:00:00'"
:end-time="1481450115"
:interval="1000"
:start-label="'Until start:'"
:end-label="'Until end:'"
label-position="begin"
:end-text="'Event ended!'"
:day-txt="'days'"
:hour-txt="'hours'"
:minutes-txt="'minutes'"
:seconds-txt="'seconds'">
</vue-countdown-timer>
</template>
<script >
export default {
name: 'Timer',
methods: {
startCallBack: function(x) {
console.log(x);
},
endCallBack: function(x) {
console.log(x);
},
},
};
</script>
now after installing the package i imported into my app.js:
import Vue from 'vue'
import VueCountdownTimer from 'vuejs-countdown-timer';
Vue.use(Vuelidate)
Vue.use(VueCountdownTimer)
window.Vue = require('vue').default
import Timer from './components/partials/timer.vue'
new Vue({
store,
components: {
Timer,
//
}, computed: {}, mount: {}
}).$mount('#app')
and after that i try to use into html template as:
<login inline-template>
<div class="page-content">
<div class="content-wrapper">
...
</div>
<Timer></Timer>
</div>
</login>
my login.js content:
import {required, minLength, maxLength} from 'vuelidate/lib/validators'
import axios from "axios";
import {route} from "../../routes";
export default {
data() {
return {
//
}
}
}
Register vue-countdown-timer globally in app.js and your custom component timer locally in login.js
app.js
import Vue from 'vue'
import VueCountdownTimer from 'vuejs-countdown-timer';
Vue.use(Vuelidate)
Vue.use(VueCountdownTimer)
window.Vue = require('vue').default
new Vue({
store,
computed: {}, mount: {}
}).$mount('#app')
Login.js
import {required, minLength, maxLength} from 'vuelidate/lib/validators'
import axios from "axios";
import {route} from "../../routes";
import Timer from '../partials/timer.vue'
export default {
data() {
return {
//
}
},
components: {
Timer
},
}
In spite of my understanding that NUXT does namespacing automatically. Because of this, I am unable to test or reference the store in any of my testing modules. Can anyone give me a tip? Maybe where I can edit the namespacing property in a Nuxt app?
Here is the code below for the component, store, and the test.
ButtonComponent.vue:
<template>
<v-container>
<v-btn #buttonClick v-model="value"></v-btn>
</v-container>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data: {
return {
value: 25
}
}
methods: {
buttonClick(event) {
this.$store.dispatch('buttonComponent/setNewValue', valuePassedIn)
},
},
}
</script>
<style scoped></style>
buttonComponent.spec.js:
import Component from '../../Component'
import { mount, createLocalVue } from '#vue/test-utils'
import expect from 'expect'
import Vue from 'vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuex)
Vue.use(Vuetify)
describe('Component', () => {
let store
let vuetify
let actions
beforeEach(() => {
actions = {
actionClick: jest.fn()
}
store = new Vuex.Store({
actions,
})
vuetify = new Vuetify()
})
it('method sends value to store when button is clicked', async () => {
const wrapper = mount(Component, {
store,
localVue,
vuetify,
})
wrapper.find('.v-btn').trigger('click')
expect(actions.actionClick).toHaveBeenCalledWith('buttonComponent/setNewValue', 25)
})
})
buttonComponent.js:
export const state = () => ({
value: 0,
})
export const mutations = {
SET_TO_NEW_VALUE(state, value) {
state.value = value
},
}
export const actions = {
setNewValue({ commit }, value) {
commit('SET_TO_NEW_VALUE', value)
},
}
Just so that I don't have to write it again here, I'll link you to an article I just posted that walks through the setup process to so you can test your Nuxt stores with Jest: https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
I know how to load asynchronous component in Vue. This
import MyComponent from '#/components/MyComponent'
export default {
components: {
MyComponent
}
}
is replaced like
export default {
components: {
MyComponent: () => import('#/components/MyComponent')
}
}
But how can I replace "named" component import, like this?
import { SweetModal } from 'sweet-modal-vue'
export default {
components: {
SweetModal
}
}
How do I import that asynchronously?
You could use at the same way, but getting your specific component:
export default {
components: {
SweetModal: () => import('sweet-modal-vue').then(m => m.SweetModal)
}
}
I recommend you to read this: Async Vue.js Component