import { ComponentType , ReactWrapper } from 'enzyme' ; import { findTestSubject , reactRouterMock } from '../helpers' ; import { mountComponentSync , mountComponentAsync , getJSXComponentWithProps , } from './mount_component' ; import { TestBedConfig , TestBed , SetupFunc } from './types' ; const defaultConfig = { defaultProps : { } , memoryRouter : { wrapComponent : true , } , store : null , } ; param Component param config example export const registerTestBed = < T extends string = string > ( Component , config ? ) : SetupFunc < T > => { const { defaultProps = defaultConfig . defaultProps , memoryRouter = defaultConfig . memoryRouter ! , store = defaultConfig . store , doMountAsync = false , } = config || { } ; let router ; const onRouter = ( _router ) => { router = _router ; if ( memoryRouter . onRouter ) { memoryRouter . onRouter ( _router ) ; } } ; /**
     * In some cases, component have some logic that interacts with the react router
     * _before_ the component is mounted.(Class constructor() I'm looking at you :)
     *
     * By adding the following lines, we make sure there is always a router available
     * when instantiating the Component.
     */ onRouter ( reactRouterMock ) ; const setup = ( props ) => { const storeToMount = typeof store === 'function' ? store ( ) : store ! ; const mountConfig = { Component , memoryRouter , store : storeToMount , props : { ... defaultProps , ... props , } , onRouter , } ; if ( doMountAsync ) { return mountComponentAsync ( mountConfig ) . then ( onComponentMounted ) ; } return onComponentMounted ( mountComponentSync ( mountConfig ) ) ; function onComponentMounted ( component ) { /**
             * ----------------------------------------------------------------
             * Utils
             * ----------------------------------------------------------------
             */ const find = ( testSubject , sourceReactWrapper = component ) => { const testSubjectToArray = testSubject . split ( '.' ) ; return testSubjectToArray . reduce ( ( reactWrapper , subject , i ) => { const target = findTestSubject ( reactWrapper , subject ) ; if ( ! target . length && i < testSubjectToArray . length - 1 ) { throw new Error ( `Can't access nested test subject "${ testSubjectToArray [ i + 1 ] }" of unknown node "${ subject }"` ) ; } return target ; } , sourceReactWrapper ) ; } ; const exists = ( testSubject , count = 1 ) => find ( testSubject ) . length === count ; const setProps = ( updatedProps ) => { if ( memoryRouter . wrapComponent !== false ) { throw new Error ( 'setProps() can only be called on a component **not** wrapped by a router route.' ) ; } if ( store === null ) { return component . setProps ( { ... defaultProps , ... updatedProps } ) ; } return component . setProps ( { children : getJSXComponentWithProps ( Component , { ... defaultProps , ... updatedProps } ) , } ) ; } ; const waitFor = async ( testSubject , count = 1 ) => { const triggeredAt = Date . now ( ) ; /**
                 * The way jest run tests in parallel + the not deterministic DOM update from React "hooks"
                 * add flakiness to the tests. This is especially true for component integration tests that
                 * make many update to the DOM.
                 *
                 * For this reason, when we _know_ that an element should be there after we updated some state,
                 * we will give it 30 seconds to appear in the DOM, checking every 100 ms for its presence.
                 */ const MAX_WAIT_TIME = 30000 ; const WAIT_INTERVAL = 100 ; const process = async ( ) : Promise < void > => { const elemFound = exists ( testSubject , count ) ; if ( elemFound ) { return ; } const timeElapsed = Date . now ( ) - triggeredAt ; if ( timeElapsed > MAX_WAIT_TIME ) { throw new Error ( `I waited patiently for the "${ testSubject }" test subject to appear with no luck. It is nowhere to be found!` ) ; } return new Promise ( ( resolve ) => setTimeout ( resolve , WAIT_INTERVAL ) ) . then ( ( ) => { component . update ( ) ; return process ( ) ; } ) ; } ; return process ( ) ; } ; /**
             * ----------------------------------------------------------------
             * Forms
             * ----------------------------------------------------------------
             */ const setInputValue = ( input , value , isAsync = false ) => { const formInput = typeof input === 'string' ? find ( input ) : ( input as ReactWrapper ) ; if ( ! formInput . length ) { throw new Error ( `Input "${ input }" was not found.` ) ; } formInput . simulate ( 'change' , { target : { value } } ) ; component . update ( ) ; if ( ! isAsync ) { return ; } return new Promise ( ( resolve ) => setTimeout ( resolve ) ) ; } ; const setSelectValue = ( select , value , doUpdateComponent = true ) => { const formSelect = typeof select === 'string' ? find ( select ) : ( select as ReactWrapper ) ; if ( ! formSelect . length ) { throw new Error ( `Select "${ select }" was not found.` ) ; } formSelect . simulate ( 'change' , { target : { value } } ) ; if ( doUpdateComponent ) { component . update ( ) ; } } ; const selectCheckBox = ( testSubject , isChecked = true ) => { const checkBox = find ( testSubject ) ; if ( ! checkBox . length ) { throw new Error ( `"${ testSubject }" was not found.` ) ; } checkBox . simulate ( 'change' , { target : { checked : isChecked } } ) ; } ; const toggleEuiSwitch = ( testSubject ) => { const checkBox = find ( testSubject ) ; if ( ! checkBox . length ) { throw new Error ( `"${ testSubject }" was not found.` ) ; } checkBox . simulate ( 'click' ) ; } ; const setComboBoxValue = ( comboBoxTestSubject , value ) => { const comboBox = find ( comboBoxTestSubject ) ; const formInput = findTestSubject ( comboBox , 'comboBoxSearchInput' ) ; setInputValue ( formInput , value ) ; comboBox . simulate ( 'keydown' , { keyCode : 13 } ) ; component . update ( ) ; } ; const getErrorsMessages = ( ) => { const errorMessagesWrappers = component . find ( '.euiFormErrorText' ) ; return errorMessagesWrappers . map ( ( err ) => err . text ( ) ) ; } ; /**
             * ----------------------------------------------------------------
             * Tables
             * ----------------------------------------------------------------
             */ param tableTestSubject const getMetaData = ( tableTestSubject ) => { const table = find ( tableTestSubject ) ; if ( ! table . length ) { throw new Error ( `Eui Table "${ tableTestSubject }" not found.` ) ; } const rows = table . find ( 'tr' ) . slice ( 1 ) . map ( ( row ) => ( { reactWrapper : row , columns : row . find ( 'td' ) . map ( ( col ) => ( { reactWrapper : col , value : col . find ( '.euiTableCellContent' ) . text ( ) , } ) ) , } ) ) ; const tableCellsValues = rows . map ( ( { columns } ) => columns . map ( ( col ) => col . value ) ) ; return { rows , tableCellsValues } ; } ; /**
             * ----------------------------------------------------------------
             * Router
             * ----------------------------------------------------------------
             */ const navigateTo = ( _url ) => { const url = _url [ 0 ] === '#' ? _url . replace ( '#' , '' ) : _url ; router . history . push ( url ) ; } ; return { component , exists , find , setProps , waitFor , table : { getMetaData , } , form : { setInputValue , setSelectValue , selectCheckBox , toggleEuiSwitch , setComboBoxValue , getErrorsMessages , } , router : { navigateTo , } , } ; } } ; return setup ; } ;