How to verify modal window is displayed or not - javascript

I'm a newbie of Vuejs.
In vue2, I already have verified whether component is displayed by specific trigger.
However, I cannot verify whether above displayed component is disappeared after another trigger.
I tried to verify wrapper.findComponent('...').exists() is false.
However, I got true when component should be disappeared.
At first, I suspect another trigger does not work well,
so I called wrapper.html(), and there is no component that I want to verify.
That's why, trigger works well, probably.
My question is, as I said at title, How to verify component existance when flag is toggled.
Below is my code.
code for test.
test('After date pick, picker is closed', async() => {
let node = document.createElement('body')
const wrapper = mount(App, {
localVue,
vuetify,
attachTo: node
})
// modal window does not appear yet.
expect(wrapper.findComponent('.v-picker').exists()).toBe(false)
// `enable` is set to true. Then, modal window is displayed.
// and verification got true
const menu = wrapper.getComponent(DateMenu)
await menu.setData({ enable: true })
expect(wrapper.findComponent('.v-picker').exists()).toBe(true)
// $emit input event cause `enable` set to false
const picker = wrapper.getComponent('.v-picker')
await picker.vm.$emit('input', '2020-01-01')
// html() returns result in which there is no elements related to toggled component
console.log(wrapper.html())
expect(menu.vm.$data.enable).toBe(false)
// test fail findComponent found a component
expect(wrapper.findComponent('.v-picker').exists()).toBe(false)
})
As reference, source code (short version).
<template>
<!-- v-model="enable" controls modal window is displayed or not-->
<v-menu v-model="enable">
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="date"
v-bind="attrs"
v-on="on"
>
</v-text-field>
</template>
<!-- This is displayed and verified existance in test. -->
<v-date-picker v-model="date" #input="input"> </v-date-picker>
</v-menu>
</template>
<script>
export default {
data: () => {
return {
// enable controls modal window is displayed or not.
// true: displayed
// false: disappear
enable: false,
};
},
methods: {
input(date) {
this.enable = false
}
}
};
</script>

I think in your test, you encounter an update applied by Vue, which is asynchronous behavior.
So after changing the reactive property (enable = false), you should wait for Vue to update DOM by adding this line:
await Vue.nextTick();
expect(wrapper.findComponent('.v-picker').exists()).toBe(false);

Vuetify renders its dialogs, menus, pickers (etc.) outside of the application wrapper, that's why wrapper.html() doesn't help you.
I believe the correct way is to use attachTo option, so you can mount your component inside the div with the data-app attribute.
If that doesn't work for you, you can also set data-app attribute on body:
let node = document.createElement('body');
node.setAttribute("data-app", true);

Related

How do i prevent duplicate calling of asyncData in Nuxt.js

nuxt.js v2.15.3.
I have used asyncData in some pages and these pages accessing by nuxt-link.
but if continuous clicking of nuxt-link (clicking interval less 1000ms), asyncData function is continuous called.
is there prevent this situation? Do i use fetch instead asyncData?
<!-- some component -->
<nuxt-link :to="`/some-page`"></nuxt-link>
// some page component
async asyncData({ $axios ) {
// Called as much as clicking.
const resp = $axios.get(....)
return { data :resp.data }
}
You can disable the link as soon as clicked:
// some component
<nuxt-link to="/some-page" #click="onLinkClick"></nuxt-link>
data() {
return {
linkClicked: false,
}
}
onLinkClick(e) {
e.preventDefault();
if (linkClicked) return;
this.linkClicked = true;
}
Of course, this solution is assuming you only want to click the link once
fetch() may indeed help since it will move out of the page and remove the blocking effect of it.
You can also implement a debounce method or look to disable <nuxt-link> is a specific condition is met. Check my answer here for debounce: https://stackoverflow.com/a/67209352/8816585
Also, feel free to add some CSS to the button (if you want to stick with asyncData), with something like #null's solution
<nuxt-link to="/some-page"
#click="onLinkClick"
:disabled="linkClicked"
>
My fancy link
</nuxt-link>

Svelte component created with JavaScript not receiving reactive updates

I dynamically create a component on button click with the following JS:
<script>
export let order;
function toggleModal(an_order){
modal_open.set(!$modal_open);
const modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
</script>
However whenever I update the order object it does not reactively reflect in the created component.
If I put the component directly in the html instead of making it dynamic like this:
<div class="modal_container-{order.id} fixed">
<OrderModal {order} />
</div>
<div class="max-w-xs w-full overflow-hidden rounded-lg shadow-md bg-white cursor-pointer" on:click="{() => toggleModal(order)}">
Then it works correctly and the elements are reactively updated.
Is there a way to make components created by JavaScript update reactively?
*** Updating to show how it is inserted ***
The function that does the update:
function deleteItem(){
order.order_items.splice(index, 1);
$order_list = $order_list;
}
As you can see I explicitly do an assignment to trigger the update which as specified works when the component is not created through javascript.
I don't know what's the context of your update function and where it's located/called from, but when creating a component programmatically/imperatively, triggering a rerender by assignment doesn't work. Instead you need to use the $set method on the component instance (docs: https://svelte.dev/docs#$set):
<script>
export let order;
let modal; // <- top level variable so it can be used in the update function
function toggleModal(an_order){
modal_open.set(!$modal_open);
modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
// ...
function deleteItem(){
order.order_items.splice(index, 1);
// imperatively update the order
modal.$set({order: $order_list});
}
// if you want updates of the `order` prop to propagate to the modal once it's set, you can do
$: modal && modal.$set({order: order});
</script>
The reason this doesn't work is because the compiler will create all the links between reactive elements during compilation. Your Modal component does not exist at that time so it is not possible to do so. This also explains why adding the Modal directly works.
One way to work around this is by using a store and pass this store into the modal.
In general in Svelte, you will not use the code as you have it. A more Svelte way to do it would be to use a bool to track if the Modal has to be shown or not:
<script>
let showModal = false
function toggleModal() {
showModal = !showModal
}
</script>
{#if showModal}
<Modal />
{/if}

Cannot queryselect a shadowRoot on button click(for toggling view)

I have a parent component ha-config-user-picker.js and child component edit-user-view.js.
Parent component:
has a mapping of users and also has the child component tag with its props.
the click event gets the shadowRoot of the child component and invokes method toggleView.
<template is="dom-repeat" items="[[users]]" as="user">
<paper-button on-click="clickEditUser">
<paper-item>
...
</paper-item>
</paper-button>
</template>
<edit-user-view hass="[[hass]]" user="[[user]]"></edit-user-view>
clickEditUser(ev) {
this.user = ev.model.user;
const el = this.shadowRoot.querySelector("edit-user-view");
el.toggleView();
}
Child Component:
<fullscreen-pop-up>
<dialog-header title="Edit User"></dialog-header>
<div class="content">
...
</div>
</fullscreen-pop-up>
toggleView = () => {
const popup = this.shadowRoot.querySelector("fullscreen-pop-up");
const dialog = popup.shadowRoot.querySelector("paper-dialog");
dialog.toggle();
}
Error:
So when I click on a mapped user. First I get error Uncaught TypeError: Cannot read property 'shadowRoot' of null. which is const popup = this.shadowRoot.querySelector("fullscreen-pop-up");. So the popup returns null.
But if i click any user again. it gets the shadowRoot and works fine.
Question: So why is it null the first time and works after that?
and how do I solve this?
Can this might be the problem?
https://github.com/Polymer/polymer/issues/5144
Let me know if you need something more to understand. :)
The toggleView method is not executed in the context of your child component, you can try to console.log(this) before using this and you will see it's not the element.
You can try something like replacing:
el.toggleView();
with
el.toggleView.call(el);
to see if this is the issue and if so refactor your code however you see fit. Please note that I did not test this at this moment, so there might be small differences.

v-img load dynamic images raise eror [Vuetify] Image load failed

In my case, I have two unrelated components. The first component has a button. The second component has <v-img> element of the Vuetify framework. The version of Vuetify which I use is 2.2.4. When the user clicks the button I pass some paraments by EventBus. I want to change the image in the second component after that click. For some reason in console I see that error: [Vuetify] Image load failed. eager property don't work. I also changed v-img to img. The result is the same. The image doesn't change. How to solve this problem for your opinion?
The first component (button click event logic):
EventBus.$emit('showLegend', {
imageName: 'http://domain/logo.png',
legendVisible: true
})
The second component:
<template>
<v-img
eager
v-if="legendVisible"
:src="legendURL">
</v-img>
</template>
<script>
import { EventBus } from '../../services/events.js'
export default {
data () {
return {
legendURL: 'http://domain/logo.png',
legendVisible: true
}
},
created () {
EventBus.$on('showLegend', data => {
if (data) {
this.legendURL = data.imageName
this.legendVisible = data.legendVisible
}
})
}
}
</script>
So you are saying that the image is bound correctly and displays, but the issue is with the change in URL after the emit via EventBus happened? If it had not worked in the first place, I'd have assumed you might need something related to require. Many times for static images that's the issue. You'd have to write
<v-img src="require('http://domain/logo.png')"></v-img>
However, I could not get this syntax to work with my own dynamic variables coming from a v-for="card in cards" myself until I used
<v-img :src="require('#/assets/' + card.imgSrc + '.jpg')"></v-img>
which I find rather dissatisfying.

How do I correctly use my inherited datas from my parent component?

I'm a newbie with VueJS and I'm setting up a rather simple website where the user can change some elements from the website, such as the website's background image.
From the admin part of the website, the user can upload an image that will be used as one of the page's background. The value is stored in a Rails API, and, when a user gets to the visitor's part, VueJS calls the settings from Rails, then displays it where I want them. I use Vue-Router, so I make the call for the settings in my App.vue and transmit the received datas to the <router-view> component. I decided to do so to load all the datas only once instead of loading them for each page change.
Here is how it looks:
App.vue
<template>
<div id="app">
<transition :name="transitionName">
<router-view :settings="settings" :me="bio"></router-view>
</transition>
</div>
</template>
<script>
export default {
data: function () {
return {
settings: null
}
},
created () {
this.$http.secured.get('/settings')
.then(response => {
this.settings = response.data
})
.catch(error => this.setError(error))
}
}
</script>
front/Landing.vue
<template>
<div id="landing" :style="{background: this.settings.landing_bg.url}">
<p>{{ this.settings.landing_bg.url }}</p>
<!-- Some other elements, not related -->
</div>
</template>
<script>
export default {
props: ['settings'],
created () {
console.log(this.settings)
}
}
</script>
<style scoped>
</style>
I have several problems with this configuration:
The first one is that I get errors saying that VueJS can't read "landing_bg" of null
But VueJS has no problem displaying the image's path in the <p> right under it
console.log(this.settings) returns null when I reload the page, but will display the settings correctly if I go to another page then back. However, no background image is set.
I tried to declare with datas() what structure this.settings will have, but VueJS tells me that it doesn't like how there are two settings declared.
I guess this is a problem with asynchronous loads, but how should I handle that? Should I use VueX?
Thank you in advance
First of all, this is redundant in your :style, change it to:
:style="{ background: settings.landing_bg.url }"
By the way, unless your landing_bg.url looks like this: url('bg_url') you might want to create a computed property:
computed: {
bgURL() { return `background: url('${this.settings.landing_bg.url}')` }
}
You might also have to add a v-if in that div to render it only after settings have been loaded. That error pops up because settings is still null when the component is created.
<div id="landing" :style="bgURL" v-if="settings">
I tried to declare with datas() what structure this.settings will have, but VueJS tells me that it doesn't like how there are two settings declared.
No, declare it on the props, like this
props: {
'settings': {
type: Object,
default: () => ({
landing_bg: {
url: '' //here you can specify a default URL or leave it blank to just remove the error
}
})
},
},

Categories

Resources