Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QDialog is not mounted in tests when used with mount() #72

Closed
dufia opened this issue Mar 27, 2019 · 19 comments
Closed

QDialog is not mounted in tests when used with mount() #72

dufia opened this issue Mar 27, 2019 · 19 comments

Comments

@dufia
Copy link

dufia commented Mar 27, 2019

Follow up on this Discord chat: https://discordapp.com/channels/415874313728688138/450312684790087691/560449585307320322

I am using 1.0.

If used with shallowMount(), it mounts the stub fine. If used with mount() the wrapper.html() doesn't have any mention of the <q-dialog> component.

Some Quasar components run in their own context (QDialog, QMenu, QTooltip). I am not sure, but maybe this is somehow causing this problem.

Usually, I would just full mount the component that uses Quasar components and then trigger the events on components like QBtn. Currently, I can't do that.

My component:

<template>
  <div>
    <q-dialog
      v-model="sendFailureModalOpened"
      persistent
      :maximized="true"
      transition-show="slide-up"
      transition-hide="slide-down"
      content-class="dark-modal"
    >
       // HTML, Vue components and other Quasar components
    </q-dialog>
  </div>
</template>

My test:

import { mount, createWrapper } from '@vue/test-utils';
import SendFailure from '@/components/Modals/SendFailure';
import { localVue, i18n } from '@/helpers/SetupLocalVue';
import Vuex from 'vuex';
import * as All from 'quasar'
const { Quasar, date } = All;

const components = Object.keys(All).reduce((object, key) => {
  const val = All[key]
  if (val && val.component && val.component.name != null) {
    object[key] = val
  }
  return object
}, {})

describe('SendFailure component', () => {
  let store;

  localVue.use(Quasar, { components });

  beforeEach(() => {
    store = new Vuex.Store();
  });

  it('renders and matches snapshot', () => {
    const wrapper = mount(SendFailure, {
      i18n,
      localVue,
      store,
    });

    expect(wrapper.element).toMatchSnapshot();
  });
});

Snapshot output:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SendFailure component renders and matches snapshot 1`] = `
<div>
  <!---->
</div>
`;
@rstoenescu
Copy link
Member

Hi,

The QDialog, QMenu and QTooltip components do not mount in the same place that they are used. They are mounted at the end of <body> when they are opened through a Quasar Portal.

What you should do is to use content-class prop, trigger QDialog to show, wait for it to be opened (attach a Vue ref and @show event handler -- this.$refs.dialog.$once('show', handlerFn)) then look in DOM for that class --> that would be your DOM node that has the QDialog content.

@dufia
Copy link
Author

dufia commented Mar 27, 2019

@rstoenescu

I've tried it, although I can tell the dialog is being opened somewhere, I cannot find a way of selecting any of its children.

In my tests, wrapper.vm.$refs.dialog exists, the dialog is opened when I set sendFailureModalOpened to true. I just cannot find a way of finding the elements within it. wrapper.find(selector) won't work obviously.

Thanks for trying to help!

@Abuntxa
Copy link

Abuntxa commented Apr 12, 2019

I use the following code to be able to test the components in the QDialog.

@rstoenescu Could it be considered the use of portal-vue?

import { createWrapper } from '@vue/test-utils'
/**
 * The dialogs are created through document.createElement in order to place the elements at the root
 * level of the page's dom (they won't be subcomponents of any view).
 * This methos allows getting access to the component of the dialog if there is any.
 * @returns {Wrapper} the vue wrapper of the dialog
 */
export const getDialogComponentWrapper = () => {
  let dialogElements = document.getElementsByClassName('q-dialog')
  // if there is any, there should be only one
  if (dialogElements
    && dialogElements.length>0
    && dialogElements[0].__vue__){
    return createWrapper(dialogElements[0].__vue__)
  }
  return undefined
}

@dufia
Copy link
Author

dufia commented Apr 12, 2019

I use the following code to be able to test the components in the QDialog.

@rstoenescu Could it be considered the use of portal-vue?

import { createWrapper } from '@vue/test-utils'
/**
 * The dialogs are created through document.createElement in order to place the elements at the root
 * level of the page's dom (they won't be subcomponents of any view).
 * This methos allows getting access to the component of the dialog if there is any.
 * @returns {Wrapper} the vue wrapper of the dialog
 */
export const getDialogComponentWrapper = () => {
  let dialogElements = document.getElementsByClassName('q-dialog')
  // if there is any, there should be only one
  if (dialogElements
    && dialogElements.length>0
    && dialogElements[0].__vue__){
    return createWrapper(dialogElements[0].__vue__)
  }
  return undefined
}

This is such a clever way of doing it, well done. I had no idea a DOM element has a corresponding __vue__ assigned to it. I'll put it to test as soon as I can. Thanks a lot @Abuntxa

@Abuntxa
Copy link

Abuntxa commented Apr 12, 2019

Well, it relies too much on the internal structures of vue and it is subject to be broken with future releases of vue....

That's why I was asking if it could be used portal-vue in the implementation of the QDialog as it seems to be posible to mock for the tests the location where these dialogs will be appearing in the dom.

https://portal-vue.linusb.org/guide/getting-started.html#use-cases

@kennyki
Copy link

kennyki commented Nov 11, 2019

I use the following code to be able to test the components in the QDialog.

@rstoenescu Could it be considered the use of portal-vue?

import { createWrapper } from '@vue/test-utils'
/**
 * The dialogs are created through document.createElement in order to place the elements at the root
 * level of the page's dom (they won't be subcomponents of any view).
 * This methos allows getting access to the component of the dialog if there is any.
 * @returns {Wrapper} the vue wrapper of the dialog
 */
export const getDialogComponentWrapper = () => {
  let dialogElements = document.getElementsByClassName('q-dialog')
  // if there is any, there should be only one
  if (dialogElements
    && dialogElements.length>0
    && dialogElements[0].__vue__){
    return createWrapper(dialogElements[0].__vue__)
  }
  return undefined
}

While this is a brilliant workaround (for now), you need to call destroy on the dialog component wrapper in an afterEach block to ensure clean state for your test cases.

let dialogWrapper = null

afterEach(() => {
  if (dialogWrapper) {
    dialogWrapper.destroy()
  }
})

it('bla bla bla', () => {
  dialogWrapper = getDialogComponentWrapper()
})

@SzNagyMisu
Copy link

Based on @Abuntxa 's awesome answer I use the following technique to test q-dialog in my component tests:

// somewhere in the setup process I create the wrapper of the component I want to test
const wrapper = mount(MyComponent, { localVue })
// in the same place I create a wrapper for `document.body`
const bodyWrapper = createWrapper(document.body)
// and when I need it, I can query the `q-dialog` on bodyWrapper
bodyWrapper.find('.q-dialog')

The good thing is that createWrapper accepts HTMLElement (I am on v1.0.0-beta.29), so no knowledge of vue's internal structures and private properties is needed.

The bad thing is - as @kennyki mentioned - that even if I create a brand new wrapper for every test case, document is not re-rendered, so I have to manually remove the q-dialog at the end of each test.

Thanks guys!

@majd-albandik
Copy link

@SzNagyMisu could u plz share the whole file ?

@apansky
Copy link

apansky commented Aug 24, 2021

Hey guys! Is there any new progress here? I'm running quasar 2 with Vue3, but I can't find any way how to test contents of Qdialog.. I also tried to create separate component and open in via dialog plugin like this
this.$q.dialog({ component: TagsDialog, componentProps: { isReadonly: this.isReadOnly, tagList: this.tagList } });
but no luck.. It seems to be createWrapper is now missing in testing tools v2, any suggestions?
Thanks!

@IlCallo
Copy link
Member

IlCallo commented Aug 25, 2021

This is a long-standing problem with Portal-based components, but still could not find a workaround
If you need to test a component which calls a dialog, stub Dialog.create or $q.dialog (depending on which you're using), as unit testing is meant to test a single component in isolation, not the interaction between components

If you need to test the dialog content, abstract the dialog inside its own component and mount that component for testing

@IlCallo IlCallo closed this as completed Jan 18, 2022
@OneFourFree
Copy link
Contributor

Hey guys! Is there any new progress here? I'm running quasar 2 with Vue3, but I can't find any way how to test contents of Qdialog.. I also tried to create separate component and open in via dialog plugin like this this.$q.dialog({ component: TagsDialog, componentProps: { isReadonly: this.isReadOnly, tagList: this.tagList } }); but no luck.. It seems to be createWrapper is now missing in testing tools v2, any suggestions? Thanks!

I've been able to get this to work with Vue 3 and quasar 2 using the attachTo property when mounting the component.

import {DOMWrapper, mount} from "@vue/test-utils"

const node = document.createElement('div')
node.setAttribute('id', 'app')
document.body.appendChild(node)

const wrapper = mount(MyComponent, {
  attachTo: '#app',
})

describe('test portal based component', () => {
  it('should display a menu item', () => {
    const documentWrapper = new DOMWrapper(document.body)
    const menu = documentWrapper.find('.q-menu')
    expect(menu.exists()).toBeTruthy()
  })
})

@Shinigami92
Copy link

Hey guys! Is there any new progress here? I'm running quasar 2 with Vue3, but I can't find any way how to test contents of Qdialog.. I also tried to create separate component and open in via dialog plugin like this this.$q.dialog({ component: TagsDialog, componentProps: { isReadonly: this.isReadOnly, tagList: this.tagList } }); but no luck.. It seems to be createWrapper is now missing in testing tools v2, any suggestions? Thanks!

I've been able to get this to work with Vue 3 and quasar 2 using the attachTo property when mounting the component.

import {DOMWrapper, mount} from "@vue/test-utils"

const node = document.createElement('div')
node.setAttribute('id', 'app')
document.body.appendChild(node)

const wrapper = mount(MyComponent, {
  attachTo: '#app',
})

describe('test portal based component', () => {
  it('should display a menu item', () => {
    const documentWrapper = new DOMWrapper(document.body)
    const menu = documentWrapper.find('.q-menu')
    expect(menu.exists()).toBeTruthy()
  })
})

@OneFourFree I think you saved me multiple hours of struggle ❤️
Works perfectly 👌

@IlCallo
Copy link
Member

IlCallo commented Mar 7, 2022

@OneFourFree do you have some time to create a PR adding you solution either to the README or as an example automatically scaffolded when adding Jest AE to a project?

@OneFourFree
Copy link
Contributor

@IlCallo yeah I'll have time to add that this week

@IlCallo
Copy link
Member

IlCallo commented Mar 8, 2022

Thanks :D

@AlphaJuliettOmega
Copy link

Latest Quasar Upgrade (the jest/test utils that are maintained independently or something) breaks this again...
for Notify, Dialog and Emits.

@Shinigami92
Copy link

@AlphaJuliettOmega Play necromancer in Diablo, but not GitHub

Please do us a favor and open a new issue

@AlphaJuliettOmega
Copy link

@Shinigami92 Ok, will try do so!

@HarisSpahija
Copy link

HarisSpahija commented May 16, 2023

Sorry for reviving the thread. Just to sum up for people still looking how to deal with QDialog testing in Vue 3 usingTest Utils V2 :

Thanks @OneFourFree updating MyDialog.spec.js

import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { beforeEach, describe, expect, it } from '@jest/globals';
import { DOMWrapper, mount } from '@vue/test-utils';
import MyDialog from './demo/MyDialog';

installQuasarPlugin();

describe('MyDialog', () => {
  beforeEach(() => {
    mount(MyDialog, {
      data: () => ({
        isDialogOpen: true,
      }),
    });
  });

  it('should mount the document body and expose for testing', () => {
    const wrapper = new DOMWrapper(document.body);

    expect(wrapper.find('.q-dialog').exists()).toBeTruthy();
  });

  it('can check the inner text of the dialog', () => {
    const wrapper = new DOMWrapper(document.body);

    expect(wrapper.find('.q-dialog').html()).toContain(
      'Custom dialog which should be tested',
    );
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests