Skip to content

Waiting for child component state #3

@gustafsilva

Description

@gustafsilva

I'm having a problem, I'm using Enzyme, Jest, react-router, React, and the enzyme-async-helpers in my project. Full project link.
But how do I wait for a child component state?
I need this, because to render my component I use the react-router.
Code of my tests and component:

/* App.jsx */
import React, { Component } from 'react';
import { Route } from 'react-router-dom';

import * as BooksAPI from './BooksAPI';
import './App.css';
import HomePage from './components/HomePage';
import SearchPage from './components/SearchPage';

class BooksApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      myBooks: [],
      booksSearch: [],
      query: '',
      loading: true,
    };
  }

  componentDidMount() {
    // Capturando todos os MEUS LIVROS da api
    BooksAPI.getAll().then((books) => {
      this.setState({
        myBooks: books,
        loading: false,
      });
    }).catch(err => console.err('[componentDidMount] App -', err));
  }

  addBook = (book, newShelf) => {
    const newBook = book;
    newBook.shelf = newShelf;

    this.setState(currentState => ({
      myBooks: currentState.myBooks.concat([newBook]),
    }));
  }

  setShelfBook = (book, books, newShelf) => {
    const newBooks = books.map((myBook) => {
      // Percorre por toda lista dos MyBooks já cadastrados para mudar shelf do livro atualizado
      if (myBook.id === book.id) {
        const newMyBook = myBook;
        newMyBook.shelf = newShelf;

        return newMyBook;
      }
      return myBook;
    });

    return newBooks;
  }

  movBookShelf = (book, newShelf) => {
    const { myBooks, booksSearch } = this.state;

    const newMyBooks = this.setShelfBook(book, myBooks, newShelf);
    const newBooksSearch = this.setShelfBook(book, booksSearch, newShelf);

    this.setState({
      myBooks: newMyBooks,
      booksSearch: newBooksSearch,
    });
  }

  delBook = (book) => {
    const { booksSearch } = this.state;
    const newBooksSearch = this.setShelfBook(book, booksSearch, 'none');

    this.setState(currentState => ({
      myBooks: currentState.myBooks.filter(myBook => myBook.id !== book.id),
      booksSearch: newBooksSearch,
    }));
  }

  checkMyBooksHaveChanged = (resultUpdate) => {
    const { myBooks } = this.state;
    const currentLengthMyBooks = myBooks.length;

    const { currentlyReading, wantToRead, read } = resultUpdate;
    const newLengthMyBooks = currentlyReading.length + wantToRead.length + read.length;

    return currentLengthMyBooks !== newLengthMyBooks;
  }

  getShelfBook = (book) => {
    const { myBooks } = this.state;

    const filterBook = myBooks.filter(myBook => myBook.id === book.id);

    if (filterBook.length > 0) {
      return filterBook[0].shelf;
    }
    return 'none';
  }

  addShelfBooksSearch = (booksSearch) => {
    const books = booksSearch.map((bookSearch) => {
      const book = bookSearch;
      book.shelf = this.getShelfBook(book);

      return book;
    });

    return books;
  }

  updateBook = (book, newShelf) => {
    // Atualizando shelf de um livro
    BooksAPI.update(book, newShelf).then((result) => {
      // Caso consiga atualizar
      if (newShelf === 'none') {
        this.delBook(book);
      } else if (this.checkMyBooksHaveChanged(result) === true) {
        this.addBook(book, newShelf);
      } else {
        this.movBookShelf(book, newShelf);
      }
    });
  }

  updateQuery = (newQuery) => {
    this.setState({
      query: newQuery,
    });

    if (newQuery !== '') {
      BooksAPI.search(newQuery).then((response) => {
        const { error } = response;
        const booksSearch = error ? [] : this.addShelfBooksSearch(response);

        this.setState({
          booksSearch,
        });
      }).catch(err => console.error('deu ruim', err));
    }
  }

  render() {
    const { myBooks, booksSearch, query } = this.state;
    return (
      <div className="app">
        <Route
          exact
          path="/"
          render={() => (
            <HomePage myBooks={myBooks} updateBook={this.updateBook} />
          )}
        />
        <Route
          path="/search"
          render={() => (
            <SearchPage
              books={booksSearch}
              query={query}
              updateQuery={this.updateQuery}
              updateBook={this.updateBook}
            />
          )}
        />
      </div>
    );
  }
}

export default BooksApp
/* App.test.jsx */
import React from 'react';
import { shallow, mount } from 'enzyme';
import { MemoryRouter } from 'react-router';
import { waitForState } from 'enzyme-async-helpers';

import App from './App';
import MyList from './components/HomePage/MyListsBooks/MyList';
import ListBooks from './components/ListBooks';
import Book from './components/ListBooks/Book';

describe('[component] App', () => {
  const PATHS = {
    homePage: '/',
    searchPage: '/search',
  };
  const CONFIG_API_REQUEST = { timeout: 5000 };
  const LIST_BOOKS = [
    {
      title: 'The Linux Command Line',
      authors: [
        'William E. Shotts, Jr.',
      ],
      imageLinks: {
        smallThumbnail: 'http://books.google.com/books/content?id=nggnmAEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api',
        thumbnail: 'http://books.google.com/books/content?id=nggnmAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api',
      },
      id: 'nggnmAEACAAJ',
      shelf: 'read',
    },
    {
      title: 'The Sebastian Thrun Handbook - Everything You Need to Know about Sebastian Thrun',
      authors: [
        'Andre Cantrell',
      ],
      imageLinks: {
        smallThumbnail: 'http://books.google.com/books/content?id=NqkKvgAACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api',
        thumbnail: 'http://books.google.com/books/content?id=NqkKvgAACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api',
      },
      id: 'NqkKvgAACAAJ',
      shelf: 'wantToRead',
    },
    {
      title: 'Travel by Design',
      authors: [
        'Marlon G. Boarnet',
        'Randall Crane',
      ],
      imageLinks: {
        smallThumbnail: 'http://books.google.com/books/content?id=f7EtoDfL6yYC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api',
        thumbnail: 'http://books.google.com/books/content?id=f7EtoDfL6yYC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api',
      },
      id: 'f7EtoDfL6yYC',
      shelf: 'currentlyReading',
    },
  ];

  it('check init state', () => {
    const initStateExptected = {
      myBooks: [],
      booksSearch: [],
      query: '',
      loading: true,
    };

    const wrapper = shallow(<App />);

    expect(wrapper.state()).toEqual(initStateExptected);
  });

  it('check loading state', async () => {
    const wrapper = shallow(<App />);
    await waitForState(wrapper, state => state.loading === false, CONFIG_API_REQUEST);

    const { loading } = wrapper.state();
    expect(loading).toBeFalsy();
  });

  describe('HomePage', () => {
    it('check my lists books', () => {
      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.homePage]}>
          <App />
        </MemoryRouter>
      ));

      expect(wrapper.exists('MyListsBooks')).toBeTruthy();
    });

    it('check three my list', () => {
      const lengthMyListExpected = 3;

      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.homePage]}>
          <App />
        </MemoryRouter>
      ));
      const myListsBooks = wrapper.find(MyList);

      expect(myListsBooks).toHaveLength(lengthMyListExpected);
    });

    it('check three lists books', () => {
      const lengthListsBooksExpected = 3;

      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.homePage]}>
          <App />
        </MemoryRouter>
      ));
      const myListsBooks = wrapper.find(ListBooks);

      expect(myListsBooks).toHaveLength(lengthListsBooksExpected);
    });

    it('check empty books rendering init state', () => {
      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.homePage]}>
          <App />
        </MemoryRouter>
      ));

      expect(wrapper.exists('Book')).toBeFalsy();
    });

    it('check books rendering', () => {
      const lengthBooksExpected = 3;

      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.homePage]}>
          <App />
        </MemoryRouter>
      ));
      const appComponent = wrapper.find(App).instance();
      appComponent.setState({ myBooks: LIST_BOOKS });
      wrapper.update();
      const booksSearch = wrapper.find(Book);

      expect(booksSearch).toHaveLength(lengthBooksExpected);
    });
  });

  describe('SearchPage', () => {
    it('check lists books', () => {
      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.searchPage]}>
          <App />
        </MemoryRouter>
      ));

      expect(wrapper.exists('ListBooks')).toBeTruthy();
    });

    it('check empty books rendering init state', () => {
      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.searchPage]}>
          <App />
        </MemoryRouter>
      ));

      expect(wrapper.exists('Book')).toBeFalsy();
    });

    it('check books rendering', () => {
      const lengthBooksExpected = 3;

      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.searchPage]}>
          <App />
        </MemoryRouter>
      ));
      const appComponent = wrapper.find(App).instance();
      appComponent.setState({ booksSearch: LIST_BOOKS });
      wrapper.update();
      const booksSearch = wrapper.find(Book);

      expect(booksSearch).toHaveLength(lengthBooksExpected);
    });

    xit('check update query search book', () => {
      const newQuery = 'react';

      const wrapper = mount((
        <MemoryRouter initialEntries={[PATHS.searchPage]}>
          <App />
        </MemoryRouter>
      ));
      const search = wrapper.find('SearchBooksBar input');
      const target = { value: newQuery };
      search.simulate('change', { target });
      wrapper.update();

      console.log(wrapper.debug());
    });
  });
});

Activity

zth

zth commented on Dec 23, 2018

@zth
Owner

Hi!

I've moved away from using this library myself in favor of react-testing-library, and I encourage you to do the same. react-testing-library is a little bit different, but it avoids testing implementation details like Enzyme (and this library) encourages you to do.

Good luck!

gustafsilva

gustafsilva commented on Dec 23, 2018

@gustafsilva
Author

Thanks @zth for the tip 👍
I'll take a look right now! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @zth@gustafsilva

        Issue actions

          Waiting for child component state · Issue #3 · zth/enzyme-async-helpers