Fran Roa Prieto
Fran Roa Prieto

Reputation: 405

Vue test utils is updating the component data but not re-rendering the dom

I'm trying to test the template of my Vue app after making an ajax request which is changing one variable of the component' data. This variable (books) is use to conditional render the gallery

CONTEXT: I want to create a gallery in order to show the books I have stored in my back end. For this, I fetching my books on mounting the component. The result of this is set in the variable books. What I'm trying to test is that, after the ajax call, the component renders the gallery with the books

PROBLEM: When the books variable is set, the div <div v-else-if='books.length > 0'>SHOW GALLERY</div> should be rendered, but the "else" div (<div v-else class='loader'>Loading</div>) is still rendered

The next two blocks of code are the component and the test itself:

BookGallery.vue (component I'm testing)

 <template>
      <v-content>
          <v-container fluid>

               /*** Conditional rendering: After the ajax request books > 0, so this div should be rendered ***/
              <div v-if='books.length > 0'>SHOW GALLERY</div>

              <div v-else class='loader'>Loading</div>
          </v-container>
      </v-content>
    </template>

    <script lang='ts'>
      import {Component} from 'vue-property-decorator';
      import {MyMixin} from '../mixin';

      @Component({components: {BookInformation}})
      export default class BookGallery extends MyMixin {
          public books: string[] = [];

          public async mounted() {

               /*** books is set as the result of the ajax request ***/

               this.books = (await this.$http.get(this.host + '/books')).data;
          }
      }
    </script>

    <style scoped lang='scss'></style>

TEST

    @test
    public async 'after calling the books, the gallery show all of them'() {

        /*** MOCKING THE RESPONSE ***/
        TestCase.OK_200({
            books: [
                { uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
                { uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
            ],
        });


        /*** MOUNTING MY COMPONENT ***/
        const wrapper = TestCase.shallowMount(BookGallery);


        /** ASSERTING **/
        await flushPromises().then(() => {

            /**  the first "expect" passes, so books > 0 = true **/
            expect(wrapper.vm.$data.books).to.eqls({
                books: [
                    { uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
                    { uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
                ],
            });

            /** This is failing. The application should read 'SHOW GALLERY' when books > 0 (something tested in the previous assert), as explained in the first comment of the component's template, but is not updating the dom, only the data **/
            see('SHOW GALLERY');
        });
    }

The QUIESTION: How can I update my DOM for my very last assert -see("SHOW GALLERY")-?

UPDATE

see Function The function only searches for a HTML element in the wrapper that vue-test-utils is using for mounting the application. In this case, as I have left it null, it is searching the text "SHOW GALLERY" over the whole HTML file

export const see = (text: string, selector?: string) => {
    const wrap = selector ? wrapper.find(selector) : wrapper;

    expect(wrap.html()).contains(text);
};

Upvotes: 20

Views: 23046

Answers (4)

Ben
Ben

Reputation: 837

I had a similar problem where child components markup was not rendered on mount and based on the other answers the following worked for me:

test('it works as expected', async () => {
  const wrapper = await mount(TestComponent);
  //expect(...)
});

Upvotes: 2

Lana
Lana

Reputation: 1267

Initially Vue Test Utils run updates synchronously. But later they removed sync mode. Here they explain reasons and show how to write tests:

Test code will change from this:

it('render text', (done) => {
    const wrapper = mount(TestComponent)
    wrapper.trigger('click')
    wrapper.text().toContain('some text')
})

To this:

it('render text', async () => {
    const wrapper = mount(TestComponent)
    wrapper.trigger('click')
    await Vue.nextTick()
    wrapper.text().toContain('some text')
})

So, to fix your issue you need to add await wrapper.vm.$nextTick() before assertion see('SHOW GALLERY');

Upvotes: 11

Luk&#225;š Irs&#225;k
Luk&#225;š Irs&#225;k

Reputation: 1132

I ran into very similar problem and solved it with await wrapper.vm.$forceUpdate(); before assertion that failed. This forces Vue to update view.

Upvotes: 17

Brice Pernet
Brice Pernet

Reputation: 221

I just solved a similar problem, using shallowMount and its sync: false option.

This deactivates sync rendering, requiring you to give the renderer some time to perform its job (await wrapper.vm.$nextTick()) where needed. Once this done, the component was re-rendered based on my reactive data, as expected.

Upvotes: 22

Related Questions