2016-12-29 1 views
1

Итак, у Jest есть очень удивительная функция, позволяющая проводить динамический повторный рендеринг компонентов в середине теста в результате изменений состояния. Это показано в React учебник на их сайте:Как вызвать повторное рендеринг в Jest, используя изменения состояния state, а не setState внутри компонентов?

https://facebook.github.io/jest/docs/tutorial-react.html

Вот дистиллированная версия соответствующего кода:

test('Link changes the class when hovered',() => { 
    const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link> 
); 

    let tree = component.toJSON(); 
    expect(tree).toMatchSnapshot(); 

    // manually trigger the callback 
    tree.props.onMouseEnter(); 

    // AUTOMATIC RE-RENDERING MID-TEST!!!! 
    tree = component.toJSON(); 
    expect(tree).toMatchSnapshot(); 

}); 

Это является удивительным, за исключением идиоматических образом, как в 2017 году, чтобы написать Реагировать это с как можно меньше компонентов состояния, что означает использование Redux. Тем не менее, я не могу заставить вышеупомянутое работать с Redux. Вот что у меня есть:

export function renderWithStore(store, Component, mapStateToProps) { 
    return renderer.create(
    <Provider store={store}> 
     <Component {...mapStateToProps(store.getState())} /> 
    </Provider> 
) 
} 

Если вам интересно, что mapStateToProps есть, это функция, обеспечиваемая для создания опоры для тестируемого компонента (так же, как вы бы с connect). Я намеренно переместил на вызов, который должен быть вставлен в пределах <Provider>, потому что, очевидно, если вы просто передали экземпляр компонента с его реквизитами (как обычно, до renderer.create), он не сможет реагировать на изменения состояния в магазине. Вот почему завод передается с его реквизитами отдельно.

И для ясности, вот что mapStateToProps выглядит следующим образом:

({total}) => { 
    return {total} 
} 

Так как мы можем достичь «живых» компонентов, как мы можем при использовании setState? Как мы можем сделать возврат renderer.create продолжать реагировать на изменения состояния редукции?

Возможно, почему-то вызов forceUpdate в ответ на store.subscribe - это решение? Я собираюсь попробовать это сейчас. Любые идеи будут высоко оценены.

UPDATE:

Я изменил мою renderWithStore функцию подписываться и вынудит обновление, как описано выше, так как:

export function renderWithStore(store, Component, mapStateToProps) { 
    let comp; 

    store.subscribe(() => { 
    comp.forceUpdate(); 
    }); 

    return renderer.create(
    <Provider ref={c => comp = c} store={store}> 
     <Component {...mapStateToProps(store.getState())} /> 
    </Provider> 
) 
} 

и это не работает. Но это прогресс. Принудительное обновление корректно вызывается, и я вызывал console.log() в пределах Component, чтобы проверить, происходит ли повторное рендеринг. И это происходит на самом деле. Однако вызов component.toJSON, похоже, повторно отображает исходный компонент, а не обновленный, который по-прежнему отличается от того, как он реагирует на setState. Как бы то ни было, похоже, что Jest внесло особое исключение для setState - испробовав, что вместо forceUpdate ...

ответ

0

Ну, я подумал, и я думаю, что придумал что-то очень полезное для реакции разработчиков на использование и сокращение, и шутка. Вот оно:

import React from 'react' 
import renderer from 'react-test-renderer' 

import {Provider} from 'react-redux' 
import createHistory from 'history/createBrowserHistory' 
import configureStore from '../src/configureStore' 

import {loadSomethingAsync} from '../src/actions/async'; 


export async function createStore({shouldLoad=true}) { 
    const history = createHistory() 
    const store = configureStore(history) 

    if(shouldLoad) { 
    await store.dispatch(loadSomethingAsync()) 
    } 

    return store 
} 

export function connect(store, Component) { 
    return (mapStateToProps) => { 
    let wrapper; 

    store.subscribe(() => { 
     wrapper.forceUpdate(); 
    }); 

    return renderer.create(
     <Provider store={store}> 
     <Wrapper 
      ref={wr => wrapper = wr} 
      store={store} 
      Component={Component} 
      mapStateToProps={mapStateToProps} 
     /> 
     </Provider> 
    ) 
    } 
} 


class Wrapper extends React.Component { 
    render() { 
    let {store, Component, mapStateToProps} = this.props 

    let props = typeof mapStateToProps === 'function' 
     ? mapStateToProps(store.getState(), store.dispatch.bind(store)) 
     : mapStateToProps //object or undefined 

    return <Component {...props} /> 
    } 
} 

ПРИМЕНЕНИЕ:

test('do something', async() => { 
    const store = await createStore() 
    const mapStateToProps = (state) => { 
    let {foo} = state 
    return {foo}; 
    } 

    const render = connect(store, MyComponent) // reusable with 
    const component = render(mapStateToProps) // different props 


    let tree = component.toJSON() 
    expect(tree).toMatchSnapshot() 

    store.dispatch({type: 'SOMETHING_HAPPENED'}}) 
    // or through a handler that does the same, eg: tree.props.onClick() 

    tree = component.toJSON() 
    expect(tree).toMatchSnapshot() // snapshot reflects state change :) 


    // now let's try re-using the enclosed component within 
    // our render function but with different mapStateToProps: 

    let tree2 = render({foo: 'bar'}); // you can also pass a plain object 
    expect(tree2).toMatchSnapshot() 
}); 

И это все. Ваши компоненты теперь «живы», то есть синхронизируются с сокращением, и вам не нужно постоянно звонить renderer.create().connect в основном похож на функцию connect(), с которой вы привыкли, но с изменением ролей компонента и преобразователя состояний, что позволяет повторно использовать один и тот же компонент с различными реквизитами в разных тестах.

Смежные вопросы