Snapshot Testing with Vue-Router - vuejs2

I'm trying to run jest-snapshot tests in a Vue app created using the Vue-cli app. When I test a component that have a router-link in it, I receive the following warning.
[Vue warn]: Unknown custom element: <router-link> - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
Following the documentation here Unit tests with View Router I can stub out the router-link, but that doesn't pass my snapshot test. Has anyone run into this issue before?

Updated: A more consistent solution
tl;dr:
The solution I found that works consistently is to create a localVue (usually outside the scope of the describe() blocks) and add the RoutherLinkStub as a component.
import { mount, RouterLinkStub, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.component('router-link', RouterLinkStub);
const wrapper = mount(Component, { localVue });
The documentation doesn't make the solution obvious because it seems to be something of a hybrid of the page you linked to and this one about RouterLinkStub.
From that page:
import { mount, RouterLinkStub } from '#vue/test-utils'
const wrapper = mount(Component, {
stubs: {
RouterLink: RouterLinkStub
}
})
That solution works in most cases. If you have a case where you need to use mount and your router-link is a not in the component being tested but the one below it, you will still get that warning. Now, if you are in that situation, it's worth reflecting if you're writing your test properly (are you testing too much rather than a small unit?), but I have a situation or two where if I shallow instead of mount, the snapshot test is worthless because it renders children as <!----> when I callexpect(wrapper.html()).toMatchSnapshot()`.
A Note on Snapshot Tests:
I haven't actually seen examples of people using the Wrapper.html() method for snapshot testing, but it really seems like the best solution in my experimenting. vue-server-renderer is a long walk for what appears to be the same result. Accessing the vm property of Wrapper gives you access to the $el property, without much trouble, but it's just a more whitespace-heavy rendering of html().

Related

Astro/Vue is executing appEntrypoint one time per component

I have few Vue components in an Astro page that are sharing the state via Pinia
I initialised the Pinia plugin as the Astro documentation mentions, via astro.config.mjs:
import {defineConfig} from 'astro/config';
import vue from '#astrojs/vue';
// https://astro.build/config
export default defineConfig({
// Enable Vue to support Vue components.
integrations: [vue({
isProduction: false,
appEntrypoint: '/src/_app'
})],
});
And then in /src/_app.ts:
import type { App } from "vue";
import { createPinia } from 'pinia'
export default (app: App) => {
console.log("Why is initializing one time per component!", app.config)
app.use(createPinia());
};
The problem is that app.use(createPinia()); is executed one time per component. So looks like because this issue, Pinia creates one Storage per component instead one sharing.
Of course, the problem is happening with all plugins. For example, app.use(InstantSearch); is executed one time per component as well, so it is doing extra calls to the server and creating X searchs plugins instances.
Here a really simple project reproducing the error, and Here the link to the stackblitz running example
Also, I created a bug issue but not sure if it is.
If this is not a bug, how to init Vue plugins per page?

Why calling async function inside <script> tags fail in Vue if no lifecycle hooks are used?

Here is the scenario, I have a component, and inside the script tag I am calling an action
<script>
if (something is true) {
await store.doSomething()
}
</script>
The component fails to mount.
When I use the onMounted hook, it seems to work.
I am beginner in Vue, but my question is what is really happening when I don't use the hook? and is it always necessary to use hook when making asynchronous calls ?
Put it inside onMounted to get it to work, although ran into other test failures afterwards.
Looking at what you wrote as of right now, you should have the Options API like this
<script>
export default {
mounted() {
// your code
},
setup() {
// can also use setup here
}
}
</script>
With Composition API (notice the setup)
<script setup>
onMounted(() => {
// your code
})
</script>
In 2., if you don't use onMounted it will be run withing the setup lifecycle as shown here.
is it always necessary to use hook when making asynchronous calls ?
Not really, but at the same time it depends on when/how you want it to run. Start by running it into mounted initially yep, easier and safer to understand overall.
Especially since setup does not re-run when re-mounted, can be quite confusing.
It also depends exactly on what is something is true exactly, regarding the lifecycle + state.
Pinia and Vitest will get their own things to think about.
I recommend reading the documentation and getting an initial grasp before proceeding.

How to use Pinia inside of an npm package?

I have similar issue as mentioned here, but with Pinia in my case. It's much harder to get Pinia to work outside of Vue components, because of "Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?", but in this case it is even harder.
Not the cleanest solution, because it requires to have Pinia installed and initialized in the project where your package will be used, but if you're doing this for internal use it is totally okay.
So, in my package it looks like this:
install(app, options = {}) {
greetings()
const { $pinia } = options
if (!$pinia) {
throw new Error(`No active Pinia instance was passed to your package`)
}
let core_store = useCoreStore($pinia)
// moar code
And in other project:
import MyPackage from '#rusinas/my-package'
app.use(ModernEditor)
const pinia = createPinia()
app.use(MyPackage, {
$pinia: pinia
})
app.use(pinia)
app.mount('#app')
I noticed that in SPA mode you may not need to provide active pinia to your package, it could figure it out itself, you just need to make sure to app.use(pinia) before you initialize you package. But this doesn't work in Nuxt SSR mode, so yeah, this workaround required :(
I think we should raise this question in Pinia's repository. I don't see why it have to work this way. Cases where you need stores outside of setup() are so often and even crucial sometimes for applications, so it should be much easier.
P.S.
Also, keep in mind the possibility of store names collisions. Names should be unique across entire application
P.P.S.
SSR solution:
plugins/MyPackage.plugin.js:
import { defineNuxtPlugin } from '#app'
import MyPackage from '#rusinas/my-package'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(MyPackage, {
$pinia: nuxtApp.$pinia,
})
})

Jest unit test fails everytime Vue component gets mounted

I am currently trying to write some unit tests for my web application that I made with Nuxt, Vue and Vuetify. I decided to use the Jest framework and the vue-test-utils to write unit tests, but it seems like Jest or vue-test-utils refuses to mount my components.
Example
Let’s take this very simple test where i shallowMount my component and see if the component renders a div:
require("jsdom-global")();
import AlbumHeader from "#/components/Album/AlbumHeader";
import { shallowMount } from "#vue/test-utils";
describe("AlbumHeader.vue", () => {
it("renders a div", () => {
const wrapper = shallowMount(AlbumHeader);
expect(wrapper.contains("div")).toBe(true);
});
});
When I run this unit test it always fails with the error:
TypeError: Cannot read property 'child' of undefined
I feel like the problem has something to do with Vuetify. But even after creating new Vuetify instances in my test and creating a localVue I still cannot get it to pass the test. I feel like I have read every article on this topic and none of them seem to help. This is also the first time I use this framework so I really do not have a clue where this problem takes place.
I hope someone can explain to me what i am doing wrong and how i can fix it, i would really appreciate that.

Custom js library(scrollMonitor) inside main Vue instance to be shared with inner components

This is Vue.js question, generally I'm trying to use 'scrollMonitor' function inside of my .vue instance(imported via main.js) but it gives me a typical 'this.scrollMonitor is not a function' error
mounted () {
let watcher = this.$scrollMonitor(this.$refs.nicer)
}
In main.js ScrollMonitor library seems to be properly imported(console shows what's expected):
import scrollMonitor from 'scrollmonitor'
Vue.use(scrollMonitor)
console.log(scrollMonitor)
Again main goal is using scrollMonitor functionality inside of .vue file(in vue component instance). Sorry if I'm missing something silly here - I'm already using some other libraries like Vue-Resource in that file so issue is not in 'filepath' but rather in the way I'm using scrollMonitor functionality, any help is much appreciated, thank you !
For those who are still looking: there is a way of adding plain js libraries to the main.js and then using them with ease globally in inner components(this is not about mixins):
import scrollmonitor from 'scrollmonitor'
Object.defineProperty(Vue.prototype, '$scrollmonitor', {
get() {return this.$root.scrollmonitor}
})
also it should be added to main Vue data object:
data () {
return { scrollmonitor }
},
And then it can be used within mounted() callback (not created() one) inside of the component itself, with scrollmonitor it may look like this(in my specific case the template had a div with ref="nicer" attribute, 'create' is a method specific to the library api):
mounted () {
this.$scrollmonitor.create(this.$refs.nicer)
}
Hooray, I hope someone may find this useful as I did!
Are you using a plain javascript library and trying to Vue.use it? That won't really work. Vue.use will only work with plugins designed to work with Vue. Import the library into the component that needs and and just use it there.
scrollMonitor(this.$refs.nicer)