I have a pair of components as follows:
ParentComponent
-- ChildComponent
where child is rendered within parent. There are no console errors on the front end.
However, a simple jest test:
it("renders child form", () => {
expect(wrapper.contains(Child)).toBe(true);
});
fails and gives the warning:
Unknown custom element: <Child> - did you register the component correctly?
Tests on the child individually pass, and the parent/child have rely similar stores and getters so the test setup is identical.
Why does this happen? Is there a way I can better debug the process the test is taking to see why the child is not appearing in the parent?
At the top of the tests there is a beforeEach that shallowMounts the parent with the mock store. ShallowMount is not the issue as the test does not run with mount.
Found the answer - Vue didn't like the use of a router to do router.push("routeName"), and would only run with this.$router.push. Vue didn't give any useful warnings so trial and error was the only way to fix it!
Related
I'm testing a Vue component which is outside a normal Vue project structure (so for the purposes of this question it can be imagined as a standalone .vue file).
In the mounted() lifecycle hook I am calling a function which follows the following format:
this.$parent.$someValue.$getSomeData();
This would ordinarily be passed down the context when the file is in its proper place, but as it is standalone, how do I mock this with Jest?
I've tried adding it into mocks when mounting, spyOn and directly assigning it, such as by using wrapper.vm.$parent.$someValue.$getSomeData = jest.fn().mockReturnValue('');
I'm currently exploring unit testing and Vue test utils particularly. I have an external library (vue-toastification) that shows a toast message when something occurs. I'd like to test that this toast has been shown, if an Error was thrown. But the problem is that the toast is rendered outside component's scope (in App.vue, as far as I can see), meanwhile with .get method we can only retrieve child components of a wrapper. So, how can I access this toast component without mounting the whole application? Maybe I should somehow use <teleport>?
I'm trying to show an activity indicator, when I go from one page to another. The target page contains many components within it, and it takes time to load. that's why I need some way to listen when all the child components are loaded, and at that moment tell my variable isBussy to be false
<template>
<StackLayout>
<ActivityIndicator :busy="isBussy" v-if="isBussy" />
<StackLayout v-else>
<Component1 />
<Component2 />
<Component3 />
<Component4 />
</StackLayout>
<StackLayout>
</template>
<script>
import Component1 from '~/components/Component1'
import Component2 from '~/components/Component2'
import Component3 from '~/components/Component3'
import Component4 from '~/components/Component4'
export default {
data() {
return {
isBussy: true
}
},
mounted() {
this.$nextTick(function() {
// Code that will run only after the
// entire view has been re-rendered
this.isBussy = false
})
}
}
</script>
this code does not work, since once the navigation is indicated from the previous page with:
#tap="$goto('otherPage', { props: { foo: bar } })"
it remains stuck on the initial page, and all the components begin to load in the background of the destination page, but without displaying the parent page, changing to this, only when the whole process ends, and never show/hide the activity indicator as expected.
By the way this expected behavior works perfectly when i do request and process them with Promises, then I turn on or off a variable in the state and it works. but I can not replicate that behavior in the navigation between pages and listen to load all the components
EDIT
Finally I achieved the desired behavior with a little trick I found on the internet
mounted() {
setTimeout(() => {
this.isBussy = false
}, 500)
},
this causes that the rendering of all the children components is delayed only a little, so that the activity indicator is shown, but not too much to produce that none of the components contained in the else block is detected and begin to rendering
There are two main ideas to understand here I think. I'll describe both.
1. General technique to Fetch Data without blocking render
It sounds like you understand this concept at the parent component level but then are asking how to do something very similar for the child components that this page contains.
The way I handle this, is in my component, I have my data default to an isLoading state. Then, in beforeMount() or mounted(), I perform my asynchronous actions and make necessary changes to my page's data.
The problem becomes entirely recursive when we look at child components. You want to make sure your child components are rendering and that any long running data fetching that needs to occur within their implementation will simply cause them to re-render once that fetching is complete.
Here is a working example: https://codesandbox.io/embed/r4o56o3olp
This example uses Nuxt. Aside from the addition fetch() and asyncData() methods, the rest of the Vue lifecycle hooks are the same here.
I use new Promise and setTimeout to demonstrate an operation that would use promises and be asynchronous. (e.g. axios.get(..))
The About page loads, and the beforeMount() lifecycle hook performs the asynchronous fetching in a way that doesn't block the page from rendering.
I use the beforeMount() hook because, according to here ( https://alligator.io/vuejs/component-lifecycle/ ), it is the first lifecycle hook that we have access to once the page's data is reactive. (So modifying this.myDataProp would trigger a re-render if {{ myDataProp }} was used in the template).
I also included a child component where I purposely made its data take twice as long to load. Since I again, am letting the component render immediately, and then I handle the fetching/updating of data in an appropriate lifecycle hook, I can manage when the end-user perceives a page to be loaded.
In my working example, the LongLoadingComponent did the same exact technique as the About page.
Once you see how to use beforeMount() or mounted() to fetch data and then update state, I think the trick is to take a moment and really think about the default state of your component. When it first renders, what should the user see before any of it's data fetching/long-running operations are completed?
Once you determine what your default (not yet loaded) component should look like, try getting that to render on your screen, and secondarily add in the logic that fetches and updates state data.
2. Listening for when a Child Component is finished rendering from a parent component
This makes use of the above technique, but includes the usage of the updated() hook and emitting a custom event ( https://v2.vuejs.org/v2/guide/components-custom-events.html
)
If you really want to listen for when your child components are finished rendering, you can $emit a custom event in your updated() hook. Perhaps something like this (in one of your child components)
if (this.dataLoaded) { this.$emit('loadedAndRendered') }
So when the child's async operations are done, it can flip it's dataLoaded property to true. If dataLoaded is used in the child's <template> somewhere, then the component should re-render (for it's "finished" state). When the child re-renders, the updated() hook should trigger. (again, see: https://alligator.io/vuejs/component-lifecycle/ ) I included the if (this.dataLoaded) part just to handle case where updated() hook might be called during intermediate data updates. (We only want to emit loadedAndRendered event if child is finished loading data/updating.)
3. Other caveats about universal nuxt applications
It wasn't until after I wrote this answer that I realized you aren't using Nuxt. However I'm adding this in case other Nuxt users happen to come across this.
I'm adding this section just because it took some focused hands-on time for me to wrap my head around. A Nuxt Universal Application does both server-side and client-side rendering. Understanding when something renders on the client vs when it was rendered on the server was a little difficult for me at first. In the working example I linked above, when you visit the about page you can also see if that component was fetched from the server or if it was just rendered by the client.
I'd recommend playing with a Page's fetch() and asyncData() methods and see how it impacts when certain things render on your screen. ( https://nuxtjs.org/api/pages-fetch/ ) ( https://nuxtjs.org/api/ ). Seeing what these methods are useful for helps me also identify what they are not useful for.
If you're using a Vuex store, I'd recommend seeing what happens when you refresh a page or use instead of a to navigate between pages. (Seeing something like the SSR schema diagram can be helpful here: https://nuxtjs.org/guide#schema )
..I have yet to fully appreciate the details of the bundling and delivery behavior that Webpack provides for a Universal Nuxt app (See right side of diagram here: https://medium.freecodecamp.org/universal-application-code-structure-in-nuxt-js-4cd014cc0baa )
What order are child components created and mounted in? I know that the lifecycle for a single component is documented here, but I couldn't find anything that described when children were created and mounted.
For example, what is the creation and mounting order for the following component?
<template>
<div class='parent'>
<child-1/>
<child-2/>
<child-3/>
</div>
</template>
I found this article to be especially helpful in explaining the order of parent/child lifecycle hooks execution. This diagram in particular offers a nice summary of the process.
Also have a look at this post by LinusBorg on the vuejs forum.
beforeCreate() and created() of the parent run first.
Then the parent’s template is being rendered, which means the child components get created.
so now the children’s beforeCreate() and created() hooks execute respectively.
these child components mount to DOM elements, which calls their beforeMount() and mounted() hooks.
and only then, after the parent’s template has finished, can the parent be mounted to the DOM, so finally the parent’s beforeMount() and mounted() hooks are called.
In Vue 3, the lifecycle hook execution order can be found in a part of its tests.
The rule of thumb is: with the exception of created hooks (which are now replaced by setup()), all hooks prefixed with before executes top-down (parent run first) while the "after" hooks execute bottom-up (children run first).
Both beforeCreated and created hooks execute top-down, however (as a child can only be created after the parent renders).
As the first response give a good view of the creation/mounting process it fails to respond for the destruction process.
here is what happen :
app created
// we add the parent
parent Created
child Created
child Mounted
parent Mounted
// now we remove the parent :
parent ready to unmount
child ready to unmount
child unMounted
parent unMounted
PS : beware that beforeDestroy & destroyed in Vue 2 became beforeUnmounted & unMounted un Vue 3
I am working on a Vue Application which has nested components.
I am using Jest for writing Unit test cases.
While executing unit test cases on a parent component, i noticed the child components are not loaded.
I am using shallow rendering in test suites.
Please share any links or tutorials on this topic.
Thanks
a) Use mount instead of shallow
b) Import the child component at the top of the spec.js file, just as you did with the parent