Невозможно проверить AsyncTypeahead с типом реакции bootstrap с ферментом - PullRequest
1 голос
/ 22 февраля 2020

Я пытаюсь проверить AsyncTypeahead из response- bootstrap -typeahead .

У меня очень простой тестовый компонент:

class AsyncTypeahead2 extends Component<Props, State> {

    constructor(props: Props) {
        super(props);
        this.state = {
            isLoading: false,
        };
    }
    render() {
        return ( <AsyncTypeahead
            isLoading={this.state.isLoading}
            onSearch={query => {
                this.setState({isLoading: true});
                fetch("http://www.myHTTPenpoint.com")
                    .then(resp => resp.json())
                    .then(json => this.setState({
                        isLoading: false,
                        options: json.items,
                    }));
            }}
            options={this.state.options}
            labelKey={option => `${option.stateName}`}
        /> )
    }
}

const url = "http://www.myHTTPenpoint.com"
fetchMock
    .reset()
    .get(
        url,
        {
            items: [
                {id:1, stateName:"Alaska"},
                {id:2, stateName:"Alabama"}
            ]
        },
    );

(Обратите внимание, что URL поддразнивается, чтобы вернуть два элемента)

Когда я запускаю это в моем сборнике рассказов, он выглядит отлично:

enter image description here

Но если я хочу проверить его (с помощью Enzyme), он не распознает всплывающие элементы .

    let Compoment =
        <div>Basic AsyncTypeahead Example
            <AsyncTypeahead2/>
        </div>

    const wrapper = mount(Compoment);
    let json = wrapper.html();


    let sel = wrapper.find(".rbt-input-main").at(0)

    sel.simulate('click');
    sel.simulate('change', { target: { value: "al" } });

    expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")

    expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."

Вместо поиска двух выпадающих предметов "items есть только один элемент с текстом" Type to Search ... ".

AynchTypeahead не обновляет DOM правильно по отношению к ферменту?

Ответы [ 2 ]

1 голос
/ 03 марта 2020

Точное решение моей проблемы в следующем коде (скопируйте и вставьте в JS файл, чтобы увидеть, как он работает).

Что нужно отметить:

  • I необходимо использовать функцию waitUntil из библиотеки asyn c -wait-till . Сам по себе fetch-mock не предоставляет возможности для тестирования асин c кода.
  • Мне нужно было добавить некрасивый хак в global.document.createRange из-за некоторой проблемы со всплывающей подсказкой с response- bootstrap - typeahead и jest.
  • используйте waitUntil для ожидания изменений внутреннего состояния компонента
  • Очень важно вызвать wrapper.update () для обновления DOM впоследствии.

..

import React, {Component} from 'react';

import waitUntil from 'async-wait-until';

import {mount} from "enzyme";
import fetchMock from "fetch-mock";
import {AsyncTypeahead} from "react-bootstrap-typeahead";


describe('Autocomplete Tests ', () => {

    test(' Asynch AutocompleteInput  ', async () => {

        class AsyncTypeaheadExample extends Component<Props, State> {

            constructor(props: Props) {
                super(props);
                this.state = {
                    isLoading: false,
                    finished: false
                };
            }

            render() {
                return (<AsyncTypeahead
                    isLoading={this.state.isLoading}
                    onSearch={query => {
                        this.setState({isLoading: true});
                        fetch("http://www.myHTTPenpoint.com")
                            .then(resp => resp.json())
                            .then(json => this.setState({
                                isLoading: false,
                                options: json.items,
                                finished: true
                            }));
                    }}
                    options={this.state.options}
                    labelKey={option => `${option.stateName}`}
                />)
            }
        }

        const url = "http://www.myHTTPenpoint.com"
        fetchMock
            .reset()
            .get(
                url,
                {
                    items: [
                        {id: 1, stateName: "Alaska"},
                        {id: 2, stateName: "Alabama"}
                    ]
                },
            );

        let Compoment =
            <AsyncTypeaheadExample/>


        // ugly hacky patch to fix some tooltip bug
        // https://github.com/mui-org/material-ui/issues/15726
        global.document.createRange = () => ({
            setStart: () => {
            },
            setEnd: () => {
            },
            commonAncestorContainer: {
                nodeName: 'BODY',
                ownerDocument: document,
            },
        });

        let wrapper = mount(Compoment);

        let sel = wrapper.find(".rbt-input-main").at(0)

        sel.simulate('click');
        sel.simulate('change', {target: {value: "al"}});
        expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")




        //now the async stuff is happening ...

        await waitUntil(() => {
            return wrapper.state().finished === true;
        }, 3000); //wait about 3 seconds

        wrapper.update() //need to update the DOM!

        expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."
    })
});

ОБНОВЛЕНИЕ


Я также могу сравнивать элементы обертки, а не делать прямое сравнение состояния:

//now the async stuff is happening ...
await waitUntil(() => {
    wrapper.update() //need to update the DOM!

    return wrapper.find(".dropdown-item").length > 1
}, 3000); //wait about 3 seconds

Это, вероятно, лучше, потому что это означает, что мне не нужно знать о компонентах компонентов.

1 голос
/ 28 февраля 2020

<AsyncTypeahead> является асинхронным . С другой стороны, simulate() является синхронным . Таким образом, в то время, когда вы получаете expect() AsyncTypeahead, вы даже не начали заполнять выпадающий список элементами <li>. Вам нужно подождать.

Не указано, но похоже, что вы используете пакет fetch-mock. Существует функция flush, которая

Возвращает Обещание, которое разрешается после разрешения всех выборок, обработанных fetch-mock

Так вот:

...

sel.simulate('click');
sel.simulate('change', { target: { value: "al" } });

await fetchMock.flush() // !!!

expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
expect(wrapper.find(".dropdown-item").length).toBe(2)

должно работать.

... Но, вероятно, не будет. Потому что

fetchMock.mock(...)
fetch(...)
await fetchMock.flush()

работает, а

fetchMock.mock(...)
setTimeout(() => fetch(...), 0)
await fetchMock.flush()

- нет. await fetchMock.flush() сразу возвращается, если не было звонка fetch. И, вероятно, не будет. Потому что <AsyncTypeahead> отклоняется.

(Кстати, вы также можете попытаться смоделировать fetch на основе теста. На всякий случай.)

Итак, я вижу два варианта:

  1. Используйте вместо пакета fetch-mock что-то еще. Где вы можете решить свои собственные Promises по завершению ложных запросов.
  2. https://tech.travelaudience.com/how-to-test-asynchronous-data-fetching-on-a-react-component-ff2ee7433d71
    import waitUntil from 'async-wait-until';
    ...
    test("test name", async () => {
        let Compoment = <AsyncTypeahead2/>
        ...
        await waitUntil(() => wrapper.state().isLoading === false);
        // or even
        // await waitUntil(() => wrapper.find(".dropdown-item").length === 2, timeout);
        expect(...)
    })
    
    Эта опция, если не симпатичная. Но, возможно, это ваш единственный вариант - вам стоит беспокоиться не только о fetch-mock. setState также асинхронный ... и похоже, что нет никакого красивого способа проверить, когда он завершил обновление состояния и DOM без изменения реального кода (что весьма нежелательно).
...