So i have a im testing component that has renders based on the route query.
<template>
<div v-if="route.query.location='us'>
//... render x if query.location=us
</>
<div v-if="route.query.location='jp'">
//... render y if query.location=jp
</>
</template>
<script>
...
const route = useRoute()
</script>
So im wondering how to change the route query using the cypress?
What i've been tried is to do router.push in the cypress but to no avail. My test :
it("render component based the location", ()=>{
const router = createRouter({
routes:route,
history:createMemoryHistory()
})
cy.mount(MyComponent,router)
cy.wrap(router.push('/path?location=us'));//NOT WORKING
...
}
Related
I am building a page with several components in it, the data for all these components I need to get by making an ajax call.
As the child components are being mounted before the data comes in I'm getting undefined errors. Whats the best way to pass the data?
Here is a simplified version of what I'm trying to achieve
Stackblitz
In that example I have one Parent.vue and inside that we have 3 child coomponents, ID, Title, Body. After getting the data from API, the child componets are not updating.
Also for making the api calls I am directly calling load method inside setup() is there any better way of doing it?
Code Snippets from the stackblitz link
<template>
<h1>Data from Parent</h1>
{{ post.id }} - {{ post.title }} - {{ post.body }}
<h1>Data from Child</h1>
<IdChild :idP="post.id" />
<TitleChild :titleP="post.title" />
<BodyChild :bodyP="post.body" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const post = ref()
const load = async () => {
let data = await fetch('https://jsonplaceholder.typicode.com/posts/1')
post.value = await data.json()
}
load()
</script>
When dealing with asynchronous data required in your components, you have basically two options:
You render the components before you get the data. It can be with default values or a loading state
<template>
<div v-if="!myAsyncData">Loading...</div>
<div v-else>{{ myAsyncData }}</div>
</template>
<script setup>
const myAsyncData = ref(null)
async function load() {
myAsyncData.value = await /* http call */
}
load() // will start to load and rendering the component
</script>
You await the response in the setup hook combined with the <Suspense> component so it starts to render only when the data is available.
Example (playground):
<!-- App.vue -->
<template>
<Suspense>
<Parent />
</Suspense>
</template>
<!-- Parent.vue -->
<template>
<div>{{ myAsyncData }}</div>
</template>
<script setup>
const myAsyncData = ref(null)
async function load() {
myAsyncData.value = await /* http call */
}
await load() // will wait this before rendering the component. Only works if the component is embebbed within a [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense.html) component.
</script>
When I have a child component like this:
<script setup>
import { defineExpose } from 'vue'
const validate = () => {
console.log('validate')
}
defineExpose({ validate })
</script>
<template>
hello
</template>
and parent component in which I use child:
<script setup>
import { ref } from 'vue'
const test = ref()
const validate = () => {
console.log('test', test.value)
}
</script>
<template>
<div ref="test">
<Child />
</div>
<button #click="validate">
click me
</button>
</template>
Is it possible to access validate method from the child component via template ref which is on the wrapper div in parent component?
EDIT:
I update my playground link in which I completed the task but I'm using parent instance instead of provide/inject:
https://sfc.vuejs.org/#eNqNU9FuozAQ/BWfXyBSYt4jqK463Un9hqOqKCypW7At29BWEf/eXcAkJVXaSEHszu4wnl0f+a0xou+A73nqSiuNZw58Z25yJVujrWdHZqFmA6utblmEpdEC/dO2nfMioYCYTvCdMp1f8DGaC0qtHCLUnhF9vMkVYyHvAR8Zizcsu2FHQqhS9EXTAT1lVXigliFXaTKpRr0YeGhNgyBFPh3lIXuWcyLIOaYZ/tJJWPKDMB2PNb6Gf/rYea8V+102snxBbpK7cI9J1sLUPJUilCaLNL7lkz+7tjDi2WmF3o+nzGfA5Xw/nZty6BjFOX/y3rh9kri6JBufndD2kOCbsJ3ysgUBrt09Wv3qwCJxzrdnHAkme7A7C6oCC/Ya56r0gpdo0fsBjxKmfm1/Kqilgr9vRjtYLVIY+TxVqdWttcU7Tv///QqDi5VgcQPnrUzXa6JN8PGUn3aN5NPnz7XFx+Vb2wtFA7ZdW7ZK9mGBXKNP+zPlP89/uQrXXDNW97JCJQfwfzqLw/B3aEehym9MXBlFmG5ANPoQR0uJJAm/oukSBQIZ+LMvPjr6hvpqFoc6YQqqEP7dgHh4UEWLrVnGItqKaPZ+XQyj11W4yMFgYTr3FAd931/un/s9fAD8ILMq
How to actually get rid of parent instance and use provide inject to achieve same result as in the playground from link above?
The ref needs to be on the actual Child element, not the parent div. The method is a property of test.value, so if the method is called "validate" you can run it with test.value.validate().
You also need to make sure the Child component is imported
Try this SFC Playground instead. The "click me" button will console.log the word "validate" which comes from the Child component.
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const test = ref()
const childFunc = () => {
test.value.validate()
}
</script>
<template>
<div>
<Child ref="test" />
</div>
<button #click="childFunc">
click me
</button>
</template>
I setup a new Vue using the router project via npm init vue#latest. Before rendering the router-view I must load some data asynchronously and pass it as props to the router-view.
Changing the App.vue file to
<script setup lang="ts">
import { RouterView } from "vue-router";
const response = await fetch("https://dummy.restapiexample.com/api/v1/employees");
const employees = await response.json();
</script>
<template>
<router-view :employees="employees" />
</template>
won't render the current router view and comes up with the warning
[Vue warn]: Component <Anonymous>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered.
at <App>
but my App.vue file does not have any parent, so I can't wrap it inside a suspense tag. But how can I fetch some data before rendering the view? ( And maybe show an error box if something failed instead )
Do I have to create an NestedApp.vue file, just to wrap it inside a suspense tag?
Do I have to come up with something like this?
<script setup lang="ts">
import { RouterView } from "vue-router";
import { ref } from "vue";
const isLoading = ref(true);
const errorOccured = ref(false);
let employees = ref([]);
fetch("https://dummy.restapiexample.com/api/v1/employees")
.then(async response => {
employees = await response.json();
isLoading.value = false;
})
.catch(() => {
errorOccured.value = true;
isLoading.value = false;
});
</script>
<template>
<div v-if="errorOccured">
Something failed!
</div>
<div v-else-if="isLoading">
Still loading!
</div>
<router-view v-else :employees="employees" />
</template>
As a sidenote what I want to do:
The app must be started with an url hash containing base64 encoded data, which is a base url. After extracting and decoding it, I must fetch some data using this url before rendering the router-view.
So maybe there are some better places for this setup code? I thought about the main.ts file but if something fails, I could display an error alert box inside the App.vue file instead.
You can load data in async created(), then use v-if to prevent rendering the dom. (You can show loading screen or spinner instead.)
new Vue({
el: '#app',
data: {
isLoaded: false,
},
async created() {
// load data (async/await)...
await new Promise(r => setTimeout(r, 2000)); // wait 2 sec...
this.isLoaded = true;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="isLoaded">Loaded! -> show data</div>
<div v-else>Loading...</div>
</div>
In suspense, I import four different components asynchronously. When I click the button to switch, I find that loading slots in suspense will only be shown for the first time, but it doesn't work when I switch again. How to solve this problem? Does Suspense not support use with dynamic routing?
<template>
<div class="app">
<button #click="index = 0">1</button>
<button #click="index = 1">2</button>
<button #click="index = 2">3</button>
<button #click="index = 3">4</button>
<Suspense>
<component :is="component[index]"></component>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue'
const son1 = defineAsyncComponent(() => import('./components/son1.vue'))
const son2 = defineAsyncComponent(() => import('./components/son2.vue'))
const son3 = defineAsyncComponent(() => import('./components/son3.vue'))
const son4 = defineAsyncComponent(() => import('./components/son4.vue'))
const component = [son1, son2, son3, son4]
const index = ref(0)
</script>
<style scoped lang="less"></style>
enter image description here
By default when a Suspense has been resolved it doesn't display the fallback content if the root component is changed. You can use the timeout prop of Suspense to make it display the fallback content if the component doesn't render before the timeout.
In your case a timeout of 0 will make sure that the fallback content is immediately displayed when the dynamic component changes :
<Suspense timeout="0">
<component :is="component[index]"></component>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
This is driving me crazy so I hope that anyone can help.
I made a Nuxt app with #nuxt/content and I'm using Netlify-CMS to create content. That all seems to work fine. However I'm trying to display a component that contains a loop of the MD-files that I have, but in the index.vue nothing of the loop is displayed.
I know (a little) about props and $emit, but as I am not triggering an event this dosen't seem to work.
Component code:
<template>
<section>
<h1>Releases</h1>
<li v-for="release of rfhreleases" :key="release.slug">
<h2>{{ release.artist }}</h2>
</li>
</section>
</template>
<script>
export default {
components: {},
async asyncData({ $content, params }) {
const rfhreleases = await $content('releases', params.slug)
.only(['artist'])
.sortBy('createdAt', 'asc')
.fetch()
return {
rfhreleases,
}
},
}
</script>
And index.vue code:
<template>
<div>
<Hero />
<Releases />
<About />
<Contact />
</div>
</template>
<script>
export default {
head() {
return {
script: [
{ src: 'https://identity.netlify.com/v1/netlify-identity-widget.js' },
],
}
},
}
</script>
If I place my component code as part of index.vue, everything work, but I would love to avoid that and thats why I'm trying to place the loop in a component.
As stated on the Nuxt documentation:
This hook can only be placed on page components.
That means asyncData only works on components under pages/ folder.
You have several options:
You use fetch instead. It's the other asynchronous hook but it's called from any component. It won't block the rendering as with asyncData so the component it will instanciated with empty data first.
You fetch your data from the page with asyncData and you pass the result as a prop to your component
<template>
<div>
<Hero />
<Releases :releases="rfhreleases" />
<About />
<Contact />
</div>
</template>
<script>
export default {
async asyncData({ $content, params }) {
const rfhreleases = await $content('releases', params.slug)
.only(['artist'])
.sortBy('createdAt', 'asc')
.fetch()
return {
rfhreleases,
}
},
}
</script>