import { renderHook , RenderHookResult } from '@testing-library/react-hooks' ; import React , { ReactNode } from 'react' ; import { CoreStart } from '../../../../../src/core/public' ; import { createKibanaReactContext } from '../../../../../src/plugins/kibana_react/public' ; import { delay } from '../utils/testHelpers' ; import { FetcherResult , useFetcher } from './use_fetcher' ; const KibanaReactContext = createKibanaReactContext ( { notifications : { toasts : { add : ( ) => { } , danger : ( ) => { } } } , } as unknown as Partial < CoreStart > ) ; interface WrapperProps { children ? : ReactNode ; callback : ( ) => Promise < string > ; args : string [ ] ; } function wrapper ( { children } ) { return < KibanaReactContext . Provider > { children } < /KibanaReactContext.Provider>; } describe ( 'useFetcher' , ( ) => { describe ( 'when resolving after 500ms' , ( ) => { let hook ; beforeEach ( ( ) => { jest . useFakeTimers ( ) ; async function fn ( ) { await delay ( 500 ) ; return 'response from hook' ; } hook = renderHook ( ( ) => useFetcher ( ( ) => fn ( ) , [ ] ) , { wrapper } ) ; } ) ; it ( 'should have loading spinner initally' , async ( ) => { expect ( hook . result . current ) . toEqual ( { data : undefined , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; } ) ; it ( 'should still show loading spinner after 100ms' , async ( ) => { jest . advanceTimersByTime ( 100 ) ; expect ( hook . result . current ) . toEqual ( { data : undefined , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; } ) ; it ( 'should show success after 1 second' , async ( ) => { jest . advanceTimersByTime ( 1000 ) ; await hook . waitForNextUpdate ( ) ; expect ( hook . result . current ) . toEqual ( { data : 'response from hook' , error : undefined , refetch : expect . any ( Function ) , status : 'success' , } ) ; } ) ; } ) ; describe ( 'when throwing after 500ms' , ( ) => { let hook ; beforeEach ( ( ) => { jest . useFakeTimers ( ) ; async function fn ( ) { await delay ( 500 ) ; throw new Error ( 'Something went wrong' ) ; } hook = renderHook ( ( ) => useFetcher ( ( ) => fn ( ) , [ ] ) , { wrapper } ) ; } ) ; it ( 'should have loading spinner initally' , async ( ) => { expect ( hook . result . current ) . toEqual ( { data : undefined , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; } ) ; it ( 'should still show loading spinner after 100ms' , async ( ) => { jest . advanceTimersByTime ( 100 ) ; expect ( hook . result . current ) . toEqual ( { data : undefined , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; } ) ; it ( 'should show error after 1 second' , async ( ) => { jest . advanceTimersByTime ( 1000 ) ; await hook . waitForNextUpdate ( ) ; expect ( hook . result . current ) . toEqual ( { data : undefined , error : expect . any ( Error ) , refetch : expect . any ( Function ) , status : 'failure' , } ) ; } ) ; } ) ; describe ( 'when a hook already has data' , ( ) => { it ( 'should show "first response" while loading "second response"' , async ( ) => { jest . useFakeTimers ( ) ; const hook = renderHook ( ( { callback , args } ) => useFetcher ( callback , args ) , { initialProps : { callback : async ( ) => 'first response' , args : [ 'a' ] , } , wrapper , } ) ; expect ( hook . result . current ) . toEqual ( { data : undefined , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; await hook . waitForNextUpdate ( ) ; expect ( hook . result . current ) . toEqual ( { data : 'first response' , error : undefined , refetch : expect . any ( Function ) , status : 'success' , } ) ; hook . rerender ( { callback : async ( ) => { await delay ( 500 ) ; return 'second response' ; } , args : [ 'b' ] , } ) ; jest . advanceTimersByTime ( 100 ) ; expect ( hook . result . current ) . toEqual ( { data : 'first response' , error : undefined , refetch : expect . any ( Function ) , status : 'loading' , } ) ; jest . advanceTimersByTime ( 500 ) ; await hook . waitForNextUpdate ( ) ; expect ( hook . result . current ) . toEqual ( { data : 'second response' , error : undefined , refetch : expect . any ( Function ) , status : 'success' , } ) ; } ) ; it ( 'should return the same object reference when data is unchanged between rerenders' , async ( ) => { const hook = renderHook ( ( { callback , args } ) => useFetcher ( callback , args ) , { initialProps : { callback : async ( ) => 'data response' , args : [ 'a' ] , } , wrapper , } ) ; await hook . waitForNextUpdate ( ) ; const firstResult = hook . result . current ; hook . rerender ( ) ; const secondResult = hook . result . current ; expect ( secondResult === firstResult ) . toEqual ( true ) ; hook . rerender ( { callback : async ( ) => { return 'second response' ; } , args : [ 'b' ] , } ) ; await hook . waitForNextUpdate ( ) ; const thirdResult = hook . result . current ; expect ( secondResult === thirdResult ) . toEqual ( false ) ; } ) ; } ) ; } ) ;