Testing a wrapped vuetify autocomplete component v menu does not open in html - vue.js

I want to test my custom component via vue-test-utils. I use vuetify autocomplete component in this case. I just wrapped with div and extract as a custom component. Nothing to do special so far.
This component is really works on development. I have no problem with it.
I used this component as shown below.
<my-component
id="brand-dropdown"
ref="brand-dropdown"
:items="brands"
labelKey="product.brand"
item-text="name"
item-value="name"
v-model="product.brandName"/>
My custom component looks like:
<template>
<div class="comboContainer">
<v-autocomplete
class="product-combobox"
ref="complete"
:items="localItems"
append-icon="$dropdownArrow"
outlined
dense
hide-details
color="#999999"
:item-text="itemText"
:item-value="itemValue"
:filter="autoCompleteFilter"
v-model="selected"
#change="onListItemSelected">
<template slot="label">
<span>{{$t(labelKey)}}</span>
</template>
<template slot="prepend-inner" v-if="showSearchIcon">
<div class="d-flex align-center justify-center"
style="height: 25px;">
<img src="~/assets/icons/search/search.png"/>
</div>
</template>
<template v-slot:no-data>
<v-list-item id="noMatchText">
<v-list-item-title>
<div :no-data-item="dataTestId">
{{ $t('selection.noMatchFound') }}
</div>
</v-list-item-title>
</v-list-item>
</template>
<template v-slot:item="{ item }">
<v-tooltip top color="transparent">
<template v-slot:activator="{ on, attrs }">
<div
v-on="item[itemText].length >36?on:null"
class="combobox-item comboboxOverFlow"
:data-testid="itemDataTestIdPrefix+item.id"
:parent-list="dataTestId">
<span>{{ item[itemText] }}</span>
</div>
</template>
<div class="popup-rectangle">
<span>{{ item[itemText] }}</span>
</div>
</v-tooltip>
</template>
</v-autocomplete>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: [],
},
labelKey: {
type: String,
default: '',
},
itemText: {
type: String,
default: '',
},
itemValue: {
type: String,
default: '',
},
value: {
type: [Number, String, Array],
},
disabled: {
type: Boolean,
default: false,
}
shouldTriggerWatch: {
type: Boolean,
default: false,
}
},
data() {
return {
selected: this.value,
showSearchIcon: false,
};
},
watch: {
value: {
immediate: true,
handler(val) {
if (this.shouldTriggerWatch) {
this.selected = val;
this.$emit('input', this.selected);
}
},
},
},
computed: {
localItems() {
if (this.items && this.items.length) {
return this.items.map(x => ({
...x,
displayName: x.lang_key ? this.$t(x.lang_key) : x.name,
}));
}
return [];
},
},
methods: {
onListItemSelected() {
this.$emit('input', this.selected);
},
autoCompleteFilter(item, queryText, itemText) {
const re = new RegExp(`(\\s+|^)(${queryText.toLocaleLowerCase()})(.*|$)`);
return re.test(itemText.toLocaleLowerCase());
},
},
};
I want to test three test case.
When I pass empty list should render as empty dropdown and render no-data slot.
<template v-slot:no-data>
<v-list-item id="noMatchText">
<v-list-item-title>
<div :no-data-item="dataTestId">
{{ $t('selection.noMatchFound') }}
</div>
</v-list-item-title>
</v-list-item>
</template>
When I pass filled list with test data I want to test all items rendered correctly
<template v-slot:item="{ item }">
<v-tooltip top color="transparent">
<template v-slot:activator="{ on, attrs }">
<div
v-on="item[itemText].length >36?on:null"
class="combobox-item comboboxOverFlow"
:data-testid="itemDataTestIdPrefix+item.id"
:parent-list="dataTestId">
<span>{{ item[itemText] }}</span>
</div>
</template>
<div class="popup-rectangle">
<span>{{ item[itemText] }}</span>
</div>
</v-tooltip>
</template>
When I search I want to provide my filter function works as expected. And autocomplete renders items according to filtered value.
This is what my test file.
function mountComponent(options) {
return mount(ComboBox, {
vuetify: new Vuetify(),
sync: false,
mocks: {
$t: key => key,
$i18n: { locale: 'en' },
},
...options,
});
}
describe('Combobox unit tests', () => {
beforeEach(() => {
document.body.setAttribute('data-app', 'true');
});
test('should create component successfully', () => {
const wrapper = mountComponent({ items: [] });
expect(wrapper.exists()).toBeTruthy();
});
test('should list zero items if the item list is empty', async () => {
const wrapper = mountComponent({
propsData: {
items: [],
labelKey: 'labelKey',
dataTestId: 'test-dropdown',
itemText: 'name',
itemValue: 'id',
},
});
const autocomplete = wrapper.find('.product-combobox');
const autocompleteControls = autocomplete.find('.v-input__slot');
autocompleteControls.trigger('click');
await wrapper.vm.$nextTick();
await flushPromises();
**// v menu cant opened !!!!**
});
test('should list third items correctly', async () => {
const testItems = [{ name: 'item1', id: 1 }, { name: 'item2', id: 2 }, { name: 'item3', id: 3 }];
const wrapper = mountComponent({
attachToDocument: true,
propsData: {
eagerProp: true,
items: testItems,
dataTestId: 'test-dropdown',
itemDataTestIdPrefix: 'test-dropdown-item-',
itemText: 'name',
itemValue: 'id',
value: null,
},
});
const slot = wrapper.find('.v-input__slot');
const input = wrapper.find('input');
// Focus input should only focus
input.trigger('focus');
expect(wrapper.vm.$children[0].isFocused).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
expect(wrapper.vm.$children[0].isMenuActive).toBe(false);
slot.trigger('click');
expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
wrapper.setProps({ searchInput: 'foo' });
expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
// v menu cant opened !!!!
// you think these expects is unnecesary. you are right I just explore this component
// if I success I'll delete all of them
});
test('should filter autocomplete search results', async () => {
});
});
I can't open v-menu in test enviroment.
I tried emit events and trigger('click')
When I console log wrapper.html() I cant see v menu is opened and all items rendered in html. Output is shown below.
<div class="comboContainer">
<div class="v-input product-combobox v-input--hide-details v-input--dense theme--light v-text-field v-text-field--enclosed v-text-field--outlined v-select v-autocomplete">
<div class="v-input__control">
<div role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-owns="list-2" class="v-input__slot" style="height: 44px;">
<fieldset aria-hidden="true">
<legend style="width: 0px;"><span>​</span></legend>
</fieldset>
<div class="v-select__slot">
<label for="input-2" class="v-label theme--light" style="left: 0px; position: absolute;"><span></span></label><input data-testid="test-dropdown" id="input-2" type="text" autocomplete="off">
<div class="v-input__append-inner">
<div class="v-input__icon v-input__icon--append"><i aria-hidden="true" class="v-icon notranslate material-icons theme--light">$dropdownArrow</i></div>
</div>
<input type="hidden">
</div>
<div class="v-menu"></div>
</div>
</div>
</div>
</div>
My problem is where is items? <div class="v-menu"></div> Why I cant see in wrapper.html. I don't use shallowMount, I use mount. Because of this I cannot write my covered test cases.
How can I simulate opened menu and rendered all items and provide some assertions?
What am I missing?
Versions
"#nuxtjs/vuetify": "1.12.1",
"#vue/test-utils": "1.0.0-beta.29",
"vue-jest": "3.0.7"

Well, probably the better way would be mocking and substituting v-autocomplete with a test component (just control values that go to it and emit fake events from it). You are not developing Vuetify, so no need to test what happens inside a component.

Related

Is there any way to pass props to component without calling it

I have 3 components like: Parent, First-child and Second-child. And I am iterating First-child in Parent component in array(it is cards), and I want to call Second-child in Parent component with First-child's props(props of one card).
My Parent component looks like this(how I am calling First-child):
``
<CardComponent
v-for="card of cards"
:key="card.urlsId"
:cardImages="card.images"
:cardTitle="card.title"
:cardDescription="card.description"
:mediaRef="card.urlsId"
:dbRef="card.dbId"
:deleteBtn="true"
:imagesWithSlider="true"
/>
And my First child is:
<template>
<div class="cards">
<v-card class="card-container">
<div class="delete-btn">
<v-btn
v-if="deleteBtn"
class="mx-2"
fab
dark
small
#click="$emit('onOpenDeleteModal')"
>
<v-icon dark> mdi-delete </v-icon>
</v-btn>
</div>
<ImageSlider
v-if="imagesWithSlider"
:imagesArray="cardImages"
:arrowBtns="false"
/>
<div class="text-container">
<h3 class="card-title">{{ cardTitle }}</h3>
<p class="card-description">{{ cardDescription }}</p>
</div>
</v-card>
</div>
</template>
<script>
export default {
props: {
cardImages: {
type: Array,
default: null,
},
cardTitle: {
type: String,
default: 'Title',
},
cardDescription: {
type: String,
default: 'Description',
},
deleteBtn: {
type: Boolean,
},
imagesWithSlider: {
type: Boolean,
},
mediaRef: {
type: String,
default: '',
},
dbRef: {
type: String,
default: '',
},
deleteModalOpen: {
type: Boolean,
},
},
emits: ['onOpenDeleteModal', 'onCloseDeleteModal'],
}
</script>
And my Second-child is:
<template>
<v-card class="modal" :loading="newCard.loading ? true : false">
<v-card class="modal-header">
<h3 v-if="addCardModal" class="header-title">New card</h3>
<h3 v-if="deleteModal" class="header-title">Delete</h3>
<v-icon aria-hidden="false" width="100%" #click="$emit('closeModal')"
>mdi-close</v-icon
>
</v-card>
<!-- Delete Modal -->
<div v-if="deleteModal" class="modal-delete">
<h3>Are you really want to delete this card ?</h3>
<div class="modal-delete-btns">
<v-btn #click="$emit('closeModal')">Cancel</v-btn>
<v-btn color="error" #click="$emit('onDeleteCard')">Delete</v-btn>
</div>
</div>
<!-- Add New Card Modal -->
<form
v-if="addCardModal"
class="modal-container"
#submit.prevent="postNewCardToDb"
>
<v-file-input
v-model="newCard.cardImages"
:clearable="false"
multiple
show-size
label="Upload card images"
#change="previewImage"
>
</v-file-input>
<v-file-input
v-model="newCard.cardVideo"
:clearable="false"
show-size
label="Upload video"
>
</v-file-input>
<div v-if="newCard.cardImageUrls.length !== 0" class="preview-image">
<ImageSlider :imagesArray="newCard.cardImageUrls" :arrowBtns="true" />
</div>
<v-text-field
v-model="newCard.cardTitle"
label="Enter card title"
></v-text-field>
<v-text-field
v-model="newCard.cardSnippet"
label="Enter card description"
></v-text-field>
<v-btn type="submit" :loading="newCard.loading ? true : false" block
>POST</v-btn
>
</form>
</v-card>
</template>
<script>
import { v4 as uuidV4, v1 as uuidV1 } from 'uuid'
export default {
/* eslint-disable no-console */
props: {
addCardModal: {
type: Boolean,
},
deleteModal: {
type: Boolean,
},
},
emits: ['closeModal', 'onDeleteCard'],
data() {
return {
newCard: {
loading: false,
cardImages: [],
cardVideo: null,
cardImageUrls: [],
cardTitle: '',
cardSnippet: '',
},
}
},
methods: {
previewImage($event) {
for (const image of event.target.files) {
this.newCard.cardImageUrls.push(URL.createObjectURL(image))
}
},
async getMediaUrlsFromStorage(newCardData) {
const cardMediaRef = uuidV1()
const cardImagesRef = await this.$fire.storage
.ref('/albums_cards/')
.child(cardMediaRef)
const videoRef = await cardImagesRef.child(uuidV4())
if (this.newCard.cardVideo) {
await videoRef.put(this.newCard.cardVideo)
const videoUrl = await videoRef.getDownloadURL()
newCardData.video = videoUrl
}
newCardData.urlsId = cardMediaRef
const promiseArr = this.newCard.cardImages.map(async (image) => {
const imageRef = cardImagesRef.child(uuidV4())
await imageRef.put(image)
const imageUrl = await imageRef.getDownloadURL()
newCardData.images.push(imageUrl)
})
await Promise.all(promiseArr)
},
async postNewCardToDb() {
this.newCard.loading = true
const newCardData = {
urlsId: '',
title: this.newCard.cardTitle,
description: this.newCard.cardSnippet,
video: '',
images: [],
}
await this.getMediaUrlsFromStorage(newCardData)
await this.$fire.database.ref('albums/cards').push(newCardData)
console.log(newCardData)
this.newCard.loading = false
this.newCard.cardTitle = null
this.newCard.cardSnippet = null
this.newCard.cardImages = []
this.newCard.cardImageUrls = []
this.newCard.cardVideo = null
},
},
}
</script>
First-child is a card component and I need to pass props of each card to Second-child without calling it. I cant call Second-child in First-child because of iteration.
I hope I expleined it well

VUE Props still undefined how to fix it?

productPage
<template>
<div id="products" class="products">
<div class="container">
<h1 class="text-center p-5">熱銷商品</h1>
<div class="row">
<div v-for="product in products" class="col-md-3">
<div class="card product-item">
<swiper :pagination="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="image in product.data().images">
<img :src="image" class="card-img-top" alt="..." />
</swiper-slide>
</swiper>
<div class="card-body">
<div
class="d-flex flex-column justify-content-between text-center"
>
<h5 class="card-title">{{ product.data().name }}</h5>
<h5 class="card-priceS">
{{ currency(product.data().price) }}$
</h5>
<p>{{ product.data().description }}</p>
</div>
<add-to-cart
:name="product.data().name"
:price="product.data().price"
:product-id="product.id"
:product-image="getImage(product.data().images)"
>
</add-to-cart>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Produtspage',
components: {
Navbar,
Login,
Swiper,
SwiperSlide,
},
props: {
msg: String,
},
setup() {
return {
modules: [Pagination],
}
},
data() {
return {
products: [],
}
},
mounted() {
this.readData()
},
methods: {
currency,
async readData() {
const querySnapshot = await getDocs(collection(db, 'products'))
querySnapshot.forEach((doc) => {
this.products.push(doc)
console.log(doc.id, ' => ', doc.data())
console.log(doc.data())
})
},
getImage(images) {
console.log(images)
return images
},
},
}
</script>
addtocart component
<template>
<div class="add-to-cart">
<button class="btn btn-success fs-6" #click="addToCart">
<i class="fa-solid fa-cart-plus mx-1"></i>加到購物車
</button>
</div>
</template>
<script>
export default {
name: 'AddToCart',
props: {
name: String,
price: String,
productId: String,
image: String,
},
data() {
return {
item: {
productName: this.name,
productPrice: this.price,
product_id: this.productId,
product_image: this.image,
// productQuantity: 1,
},
}
},
}
</script>
I checked the Vue Devtools, only image no props success, other data does have correct props, the image prop is still undefined, the image data is url and when I console.log(images), the data is shown, but props are still undefined.
I use firestore for the project, did I do anything wrong here?
you are using the wrong name for the binded attribute that dosnt match the prop name :
you are using :product-image in productPage
<add-to-cart
:name="product.data().name"
:price="product.data().price"
:product-id="product.id"
:product-image="getImage(product.data().images)"
>
so you have to match it with productImage instead of product-image as a prop name in the addtocart component
props: {
name: String,
price: String,
productId: String,
productImage: String, // appropriate prop name
},

want to use vuetify snackbar as a global custom component in vuejs

i used snackbar to show success messages in vuejs. i want to make a global custom snackbar component.
<template>
<div name="snackbars">
<v-snackbar
v-model="snackbar"
:color="color"
:timeout="timeout"
:top="'top'"
>
{{ text }}
<template v-slot:action="{ attrs }">
<v-btn dark text v-bind="attrs" #click="snackbar = false">
Close
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
props: {
snackbar: {
type: Boolean,
required: true,
},
color: {
type: String,
required: false,
default: "success",
},
timeout: {
type: Number,
required: false,
default: 3000,
},
text: {
type: String,
required: true,
},
},
};
</script>
then i import this as a component in my every form like this.
<SnackBar :snackbar="snackbar" :color="color" :text="text" />
but my issue is i can't use snackbar as a prop in my child component. it shows me this error.
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "snackbar"
how can i fix this issue. can anyone help me?
I realize this is old, but thanks to google, I am going to add my solution.
I use this, because I don't see the point of using vuex for a snackbar. It's more work then needed.
Create a vue component named vtoast
<template>
<v-snackbar
:color="color"
:timeout="timer"
v-model="showSnackbar"
bottom
right
>
<v-icon left>{{icon}}</v-icon>{{message}}
</v-snackbar>
</template>
<script>
export default {
name: "vtoast",
data() {
return{
showSnackbar: false,
message: '',
color: 'success',
icon: 'mdi-check',
timer: 3000
}
},
methods:{
show(data) {
this.message = data.message || 'missing "message".'
this.color = data.color || 'success'
this.timer = data.timer || 3000
this.icon = data.icon || 'mdi-check'
this.showSnackbar = true
}
}
}
</script>
Somewhere in the root of your main app, add the following. (I usually put mine in App.vue)
<template>
...
<!-- toast -->
<vtoast ref="vtoast"/>
...
</template>
<script>
import vtoast from '#/your/vtoast/directory/vtoast'
export default{
name: 'App', //or whatever your root is
components:{
vtoast
},
mounted() {
this.$root.vtoast = this.$refs.vtoast
},
}
</script>
And access it like so...
this.$root.vtoast.show()
this.$root.vtoast.show({message: 'Ahoy there!'})
i found a way to fix my solution using vuex.
<template>
<div name="snackbars">
<v-snackbar v-model="show" :color="color" :timeout="timeout" :top="'top'">
{{ text }}
<template v-slot:action="{ attrs }">
<v-btn dark text v-bind="attrs" #click="show = false">
Close
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
created() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === "snackbar/SHOW_MESSAGE") {
this.text = state.snackbar.text;
this.color = state.snackbar.color;
this.timeout = state.snackbar.timeout;
this.show = true;
}
});
},
data() {
return {
show: false,
color: "",
text: "",
timeout: 0,
};
},
};
</script>
in my vuex module i wrote like this
export default {
namespaced: true,
state: {
text: "",
color: "",
timeout: "",
},
mutations: {
SHOW_MESSAGE(state, payload) {
state.text = payload.text;
state.color = payload.color;
state.timeout = payload.timeout;
},
},
actions: {
showSnack({ commit }, payload) {
commit("SHOW_MESSAGE", payload);
},
},
};
then i import snackbar child component into my parent component and send data like this.
...mapActions("snackbar", ["showSnack"]),
saveDetails() {
this.showSnack({
text: "Successfully Saved!",
color: "success",
timeout: 3500,
});
}
Another solution is to use a computed value with getter and setter.
Using options api
<template>
<v-snackbar v-model="show" :color="color">
{{ message }}
<template v-slot:action="{ attrs }">
<v-btn text v-bind="attrs" #click="show = false">Close</v-btn>
</template>
</v-snackbar>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
message: 'snackbar/message',
color: 'snackbar/color'
}),
show: {
get() {
return this.$store.state.snackbar.show
},
set(v) {
this.$store.commit('snackbar/SET_SHOW', v)
}
}
}
}
</script>
Using composition api plugin
<template>
<v-snackbar v-model="show" :color="color">
{{ message }}
<template v-slot:action="{ attrs }">
<v-btn text v-bind="attrs" #click="show = false">Close</v-btn>
</template>
</v-snackbar>
</template>
<script>
import { defineComponent, computed } from '#vue/composition-api';
export default defineComponent({
setup(_props, { root }) {
const show = computed({
get: () => root.$store.state.snackbar.show,
set: (v) => root.$store.commit('snackbar/SET_SHOW', v),
});
const message = computed(() => root.$store.state.snackbar.message);
const color = computed(() => root.$store.state.snackbar.color);
return {
show,
message,
color,
};
},
});
</script>
A better implementation using composables here https://gist.github.com/wobsoriano/2f3f0480f24298e150be0c13f93bac20
You are having a prop and the same in data.
remove snackbar from data() as it is available from prop.
<script>
export default {
props: {
snackbar: {
type: Boolean,
required: true,
},
color: {
type: String,
required: false,
default: "success",
},
timeout: {
type: Number,
required: false,
default: 3000,
},
text: {
type: String,
required: true,
},
}
};
</script>
This is what I did with Options API with mere props and events;
Here is the Snackbar.vue component
<template>
<div class="text-center">
<v-snackbar
transition="true"
bottom
right
v-model="show"
:color="snackbar.color"
:timeout="snackbar.timeout"
class="snackbar-shadow"
>
<div class="d-flex align-start alert-notify">
<v-icon size="24" class="text-white mr-5">{{ snackbar.icon }}</v-icon>
<p class="mb-0">
<span class="font-size-root font-weight-600">{{
snackbar.title
}}</span>
<br />
{{ snackbar.message }}
</p>
</div>
<template v-slot:action="{ attrs }">
<v-btn
icon
elevation="0"
max-width="136"
:ripple="false"
height="43"
class="font-weight-600 text-capitalize py-3 px-6 rounded-sm"
color="rgba(255,255,255, .85)"
text
v-bind="attrs"
#click="show = false"
>
<v-icon size="13">fas fa-times</v-icon>
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
name: "snackbar",
props: {
snackbar: Object,
},
computed: {
show: {
get() {
return this.snackbar.visible;
},
set(value) {
this.$emit("closeSnackbar", value);
},
},
},
};
</script>
Here is the App.vue component
<template>
<!-- Snackbar -->
<snackbar :snackbar="snackbar" #closeSnackbar="SnackbarClose"></snackbar>
</template>
<script>
export default {
name: "app",
data() {
return {
snackbar: {
visible: false,
timeout: 2000,
color: "#11cdef",
title: "Hello",
message: null,
icon: "fas fa-bell",
},
};
},
created: { this.SnackbarShow(); }
methods: {
SnackbarShow() {
this.snackbar.visible = true;
this.snackbar.message = "Hola!👋 I'm a snackbar";
},
SnackbarClose() {
this.snackbar.visible = false;
},
},
};
</script>

Missing required prop in Vue.js

I am new to vue.js so maybe i'm missing something obvious. I have created 2 components Content.vue & ViewMore.vue. I am passing property "genre" which is inside array "animesByGenre" in Content.vue to ViewMore.vue but somehow it is not working.
Here is the Content.vue component:
<div v-for="(animesAndGenre, index) in animesByGenres" :key="index"
id="row1" class="container">
<h5>
{{animesAndGenre.genre.toUpperCase()}}
<button class="viewMore" v-bind:genre="animesAndGenre.genre"><router-link :to="{name: 'viewmore'}">view more</router-link></button>
</h5>
<vs-row vs-justify="center" class="row">
<vs-col v-for="(anime, i) in animesAndGenre.animes" :key="i"
vs-type="flex" vs-justify="center"
vs-align="center" vs-w="2" class="animeCard">
<vs-card actionable class="cardx">
<div slot="header" class="cardTitle">
<strong>
{{anime.attributes.canonicalTitle}}
</strong>
</div>
<div slot="media">
<img :src="anime.attributes.posterImage.medium">
</div>
<div>
<span>Rating: {{anime.attributes.averageRating}}</span>
</div>
<div slot="footer">
<vs-row vs-justify="center">
<vs-button #click="addAnimeToWatchlist(anime)"
color="primary" vs-type="gradient" >
Add to Watchlist
</vs-button>
</vs-row>
</div>
</vs-card>
</vs-col>
</vs-row>
</div>
<script>
import ViewMore from './ViewMore.vue';
import axios from 'axios'
export default {
name: 'Content',
components: {
'ViewMore': ViewMore,
},
data () {
return {
nextButton: false,
prevButton: false,
viewMoreButton: false,
results: '',
animes: '',
genres: ['adventure', 'action', 'thriller', 'mystery', 'horror'],
animesByGenres: []
}
},
created() {
this.getAnimes();
// this.getRowAnime();
this.genres.forEach( (genre) => {
this.getAnimeByGenres(genre);
});
},
}
</script>
Here is the ViewMore.vue component (i'm just trying to log genre for now):
<template>
<div>
</div>
</template>
<script>
import axios from 'axios'
export default {
props: {
genre: {
type: String,
required: true,
},
},
data() {
return {
allAnimes: '',
}
},
created() {
console.log(this.genre);
}
}
</script>
Passing props to routes doesn't work like that. Right now, all this code is doing is applying the genre prop to the button itself, not to the route it's going to. You'll need to add the genre to the URL as a param (/viewmore/:genre/), or as a part of the query (/viewmore?genre=...). See this page for how that works

Using a vue-apollo component with different queries on the same page

I would like to make a wrapper component using Apollo, which receives different GraphQL queries and variables within props, queries GraphQL source and and passes response to its child component. I would like to use this component twice on a page with different queries.
But I think I'm stuck at some point. I end up having both instances of the component working with same inputs: query prop of the first dropdown component is being used in all dropdowns on the page. Although they have a different scope, different keywords and items, all dropdowns on the page are using the query of first dropdown.
Here is my DropdownSearch component. It passes keyword from searchbar component to query-list component:
<template>
<div class="dropdown-search">
<div class="dropdown-search-display" #click="toggleDropdown">
<span>{{value[nameProp]}}</span>
<span class="dropdown-search-dropdown-toggle">
<i v-if="!isOpen" class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isOpen" class="fa fa-caret-up" aria-hidden="true"></i>
</span>
</div>
<div :class="{ 'dropdown-search-dropdown': true, 'dropdown-search-dropdown--open': isOpen}" >
<search-bar v-model="searchKeyword" class="dropdown-search-searchbar"></search-bar>
<div class="dropdown-search-list">
<query-list
:query="query"
:keyword="searchKeyword"
:listItemComponent="ListItem"
></query-list>
</div>
</div>
</div>
</template>
<script>
const ListItem = {
template: '<div class="dropdown-search-list-item">{{record.name}}</div>',
props: {
record: { required: true },
},
};
export default {
name: 'dropdownSearch',
props: {
name: String,
nameProp: String,
value: Object,
query: Object,
},
components: {
'dropdown-list-item': ListItem,
},
data() {
return {
isOpen: false,
searchKeyword: '',
ListItem,
};
},
methods: {
toggleDropdown() {
this.isOpen = !this.isOpen;
},
},
};
</script>
QueryList component, which is using Apollo to make queries and displays a list of results:
<template>
<div class="">
<loading-indicator :isLoading="loading > 0"></loading-indicator>
<ul class="app__list">
<li class="app__list-item" v-for="item in items" :key="item.id">
<component
:is="listItemComponent"
:record="item"
></component>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'queryList',
props: {
query: Object,
keyword: String,
listItemComponent: { required: true },
},
data() {
return {
items: [],
loading: 0,
};
},
apollo: {
items: {
query() {
return this.query;
},
variables() {
return {
keyword: this.keyword || '',
};
},
loadingKey: 'loading',
},
},
};
</script>
This is how I use Dropdown Search components on a page:
<label class="edit-record-field">
<span class="edit-record-field-label">Category</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.category"
:nameProp="'name'"
:query="getCategories"
></dropdown-search>
</label>
<label class="edit-record-field">
<span class="edit-record-field-label">Location</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.location"
:nameProp="'name'"
:query="getLocation"
></dropdown-search>
</label>
I'm looking for a solution. I would be grateful if you could help me to make my components working.