html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{font:87.5%/1.2 -apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;box-sizing:border-box;overflow-y:scroll;}*{box-sizing:inherit;}*:before{box-sizing:inherit;}*:after{box-sizing:inherit;}body{color:var(--app-foreground);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:normal;word-wrap:break-word;font-kerning:normal;-moz-font-feature-settings:"kern", "liga", "clig", "calt";-ms-font-feature-settings:"kern", "liga", "clig", "calt";-webkit-font-feature-settings:"kern", "liga", "clig", "calt";font-feature-settings:"kern", "liga", "clig", "calt";}img{max-width:100%;margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}h1{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:2rem;line-height:1.1;}h2{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1.51572rem;line-height:1.1;}h3{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1.31951rem;line-height:1.1;}h4{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1rem;line-height:1.1;}h5{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:0.87055rem;line-height:1.1;}h6{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;color:var(--app-purple);font-family:-apple-system,'BlinkMacSystemFont','Segoe UI','Roboto','Oxygen-Sans','Ubuntu','Cantarell','Helvetica Neue',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:0.81225rem;line-height:1.1;}hgroup{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}ul{margin-left:1.2rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;list-style-position:outside;list-style-image:none;}ol{margin-left:1.2rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;list-style-position:outside;list-style-image:none;}dl{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}dd{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}p{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}figure{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}pre{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;font-size:0.85rem;line-height:1.2rem;}table{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;font-size:1rem;line-height:1.8rem;border-collapse:collapse;width:100%;}fieldset{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}blockquote{margin-left:1.2rem;margin-right:1.2rem;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}form{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}noscript{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}iframe{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}hr{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:calc(1.2rem - 1px);background:hsla(0,0%,0%,0.2);border:none;height:1px;}address{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.2rem;}b{font-weight:bold;}strong{font-weight:bold;}dt{font-weight:bold;}th{font-weight:bold;}li{margin-bottom:calc(1.2rem / 2);}ol li{padding-left:0;}ul li{padding-left:0;}li > ol{margin-left:1.2rem;margin-bottom:calc(1.2rem / 2);margin-top:calc(1.2rem / 2);}li > ul{margin-left:1.2rem;margin-bottom:calc(1.2rem / 2);margin-top:calc(1.2rem / 2);}blockquote *:last-child{margin-bottom:0;}li *:last-child{margin-bottom:0;}p *:last-child{margin-bottom:0;}li > p{margin-bottom:calc(1.2rem / 2);}code{font-size:1em;line-height:1.4em;font-family:'Fira Code', monospace;}kbd{font-size:0.85rem;line-height:1.2rem;}samp{font-size:0.85rem;line-height:1.2rem;}abbr{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}acronym{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}abbr[title]{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;text-decoration:none;}thead{text-align:left;}td,th{text-align:left;border-bottom:1px solid hsla(0,0%,0%,0.12);font-feature-settings:"tnum";-moz-font-feature-settings:"tnum";-ms-font-feature-settings:"tnum";-webkit-font-feature-settings:"tnum";padding-left:0.8rem;padding-right:0.8rem;padding-top:0.6rem;padding-bottom:calc(0.6rem - 1px);}th:first-child,td:first-child{padding-left:0;}th:last-child,td:last-child{padding-right:0;}a{color:inherit;}a:focus{outline:2px solid var(--app-cyan);outline-offset:0;}a:visited{color:var(--app-cyan);}code.inline{line-height:1em;background:none;}.bOFKFr{fill:currentColor;height:2em;margin:0.25em;-webkit-transform:none;-ms-transform:none;transform:none;}/*!sc*/ .etECvA{fill:currentColor;height:1em;margin:0.25em;-webkit-transform:none;-ms-transform:none;transform:none;}/*!sc*/ .crLEvQ{fill:currentColor;height:1em;margin:0.25em;-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg);}/*!sc*/ .kpJAMR{fill:currentColor;height:1em;margin:0.25em;-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);}/*!sc*/ data-styled.g3[id="icon__Icon-sc-1w03ldy-0"]{content:"bOFKFr,etECvA,crLEvQ,kpJAMR,"}/*!sc*/ .empDfs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:1em;border:1px solid var(--app-foreground);border-radius:var(--app-border-radius);-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;}/*!sc*/ data-styled.g4[id="example__Render-sc-1u0tgm1-0"]{content:"empDfs,"}/*!sc*/ .iXseCQ{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;margin-top:0.25em;}/*!sc*/ data-styled.g5[id="example__FlexRight-sc-1u0tgm1-1"]{content:"iXseCQ,"}/*!sc*/ .eBWeFI{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}/*!sc*/ data-styled.g6[id="example__CodeSandboxLink-sc-1u0tgm1-2"]{content:"eBWeFI,"}/*!sc*/ .iyGSwu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:0;padding:0;color:var(--app-foreground);background:none;border:none;cursor:pointer;}/*!sc*/ .iyGSwu:focus,.iyGSwu:active{color:var(--app-cyan);outline:2px solid var(--app-cyan);}/*!sc*/ data-styled.g7[id="example__ResetButton-sc-1u0tgm1-3"]{content:"iyGSwu,"}/*!sc*/ .pTvQc{max-width:100%;overflow:auto;--grvsc-border-radius:var(--app-border-radius);--grvsc-padding-h:1em;--grvsc-padding-v:1em;}/*!sc*/ .dense .default-mdx-provider__pre-sc-15gdqwx-0{margin:0;}/*!sc*/ data-styled.g8[id="default-mdx-provider__pre-sc-15gdqwx-0"]{content:"pTvQc,"}/*!sc*/ .iojkbB{border-left:2px solid var(--app-pink);padding-left:0.5em;}/*!sc*/ data-styled.g9[id="default-mdx-provider__blockquote-sc-15gdqwx-1"]{content:"iojkbB,"}/*!sc*/ .hoMCCU.slug{font-size:0.75em;margin-left:0.5em;display:none;-webkit-text-decoration:none;text-decoration:none;}/*!sc*/ *:hover > .default-mdx-provider__a-sc-15gdqwx-2.slug{display:unset;}/*!sc*/ data-styled.g10[id="default-mdx-provider__a-sc-15gdqwx-2"]{content:"hoMCCU,"}/*!sc*/ .koMXVa{margin:3em 0 1em;text-align:center;}/*!sc*/ data-styled.g12[id="footer__Container-sc-1pf7cvd-0"]{content:"koMXVa,"}/*!sc*/ .dvvVYp{color:var(--app-comment);}/*!sc*/ data-styled.g13[id="footer__Text-sc-1pf7cvd-1"]{content:"dvvVYp,"}/*!sc*/ .kCIvmz{color:var(--app-comment);}/*!sc*/ .kCIvmz:visited{color:var(--app-comment);}/*!sc*/ data-styled.g14[id="footer__Link-sc-1pf7cvd-2"]{content:"kCIvmz,"}/*!sc*/ .eeUdUW{display:grid;grid-template-areas:'title expand' 'links links';grid-template-columns:1fr auto;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}/*!sc*/ @media (min-width:1200px){.eeUdUW{grid-template-areas:'title' 'links';grid-template-columns:unset;}}/*!sc*/ data-styled.g15[id="navigation__Container-sc-18boyty-0"]{content:"eeUdUW,"}/*!sc*/ .khCaEg{grid-area:title;margin:0;}/*!sc*/ @media (min-width:1200px){.khCaEg{margin-bottom:0.5em;}}/*!sc*/ data-styled.g16[id="navigation__Title-sc-18boyty-1"]{content:"khCaEg,"}/*!sc*/ .jyqapB{-webkit-text-decoration:none;text-decoration:none;color:var(--app-foreground);}/*!sc*/ .jyqapB:visited{color:var(--app-foreground);}/*!sc*/ data-styled.g17[id="navigation__TitleLink-sc-18boyty-2"]{content:"jyqapB,"}/*!sc*/ .iKXeho{grid-area:expand;color:var(--app-foreground);width:50px;height:50px;background:none;border:none;margin:0;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}/*!sc*/ .iKXeho:focus,.iKXeho:active{color:var(--app-pink);outline:2px solid var(--app-pink);}/*!sc*/ @media (min-width:1200px){.iKXeho{display:none;}}/*!sc*/ data-styled.g18[id="navigation__Expand-sc-18boyty-3"]{content:"iKXeho,"}/*!sc*/ .ktoYoz{grid-area:links;display:none;}/*!sc*/ @media (min-width:1200px){.ktoYoz{display:unset;}}/*!sc*/ data-styled.g19[id="navigation__Links-sc-18boyty-4"]{content:"ktoYoz,"}/*!sc*/ .bwEVJA{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:1.2em;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}/*!sc*/ data-styled.g20[id="prev-next__StyledLinkToSlug-sel85m-0"]{content:"bwEVJA,"}/*!sc*/ .fhnjmd{display:grid;grid-template-areas:'left-void nav right-void' 'left-void content right-void';grid-template-columns:1fr minmax(0,85ch) 1fr;padding:0 0.5rem;}/*!sc*/ @media (min-width:1200px){.fhnjmd{grid-template-areas:'nav content void';grid-template-columns:1fr 85ch 1fr;padding:1rem;max-width:150ch;margin:0 auto;}}/*!sc*/ data-styled.g21[id="default__Grid-cc4rlb-0"]{content:"fhnjmd,"}/*!sc*/ .giaaSE{grid-area:nav;}/*!sc*/ data-styled.g22[id="default__NavigationSlot-cc4rlb-1"]{content:"giaaSE,"}/*!sc*/ .hVUruk{grid-area:content;}/*!sc*/ data-styled.g23[id="default__Content-cc4rlb-2"]{content:"hVUruk,"}/*!sc*/ .iBPGgH{display:grid;grid-template-areas:'next' 'prev';}/*!sc*/ @media (min-width:576px){.iBPGgH{grid-template-areas:'prev void next';grid-template-columns:auto 1fr auto;}}/*!sc*/ data-styled.g24[id="default__Paging-cc4rlb-3"]{content:"iBPGgH,"}/*!sc*/ .bDSAqh{grid-area:prev;}/*!sc*/ data-styled.g25[id="default__StyledPrev-cc4rlb-4"]{content:"bDSAqh,"}/*!sc*/ .dmuBWa{grid-area:next;justify-self:flex-end;}/*!sc*/ data-styled.g26[id="default__StyledNext-cc4rlb-5"]{content:"dmuBWa,"}/*!sc*/

Testing

// pseudo tests using AAA, read it as prose

describe('wrap everything in a describe', () => {
  /* 1 */ beforeEach(() => {
    // setup needed for every test
    // ARRANGE
  })

  describe('group main functionality in 1st level of describes', () => {
    /* 2 */ beforeEach(() => {
      // do something to change the state
      // use a `beforeEach` even if there is only one `it`
      // it will be easier to extend later
      // ARRANGE & ACT
    })

    it('should respond to the change', () => {
      // test the outcome
      // ASSERT
    })

    describe('use nested describes to dive into branches', () => {
      /* 3 */ beforeEach(() => {
        // do something that changes the state further
        // ARRANGE & ACT
      })

      it('should respond to the change', () => {
        // assertion given that 1, 2, 3 executed
        // ASSERT
      })

      it('should follow the structure of your code', () => {
        // it should be easy to find where new tests need to go
        // ASSERT
      })
    })
  })

  // #region events
  describe('example events', () => {
    describe('when the user types', () => {
      beforeEach(() => {
        // find input element
        // ARRANGE
        // fire change event so that it is invalid
        // ACT
      })

      it('should validate input', () => {
        // find error
        // match text in error
        // ASSERT
      })

      describe('when the input is valid', () => {
        beforeEach(() => {
          // fire a change event so that the input is valid
          // ACT
        })

        it('should validate input', () => {
          // find error
          // expect that there are none
          // ASSERT
        })

        describe('when the user submits', () => {
          beforeEach(() => {
            // find submit button
            // click it
            // ARRANGE & ACT
          })

          it('should call api', () => {
            // expect mock to have been called with form data
            // ASSERT
          })
        })
      })
    })
  })
  // #endregion

  // #region a11y
  describe('example a11y', () => {
    describe('when the accordion is closed', () => {
      beforeEach(() => {
        // find the toggle button
        // ARRANGE
      })

      it('should have aria-expanded=false', () => {
        // expect the toggle button to have the attribute
        // ASSERT
      })

      describe('when the accordion is open', () => {
        beforeEach(() => {
          // click the toggle
          // ACT
        })

        it('should have aria-expanded=true', () => {
          // expect the toggle button to have the attribute
          // ASSERT
        })

        describe('when the accordion is closed', () => {
          beforeEach(() => {
            // click the toggle
            // ACT
          })

          it('should have aria-expanded=false', () => {
            // expect the toggle button to have the attribute
            // ASSERT
          })
        })
      })
    })
  })
  // #endregion

  // #region non-happy
  describe('example non-happy paths', () => {
    describe('when the api times out', () => {
      beforeEach(() => {
        // use mock timers
        // set mock to return unresolved promise
        // ARRANGE
        // action that calls into api
        // ACT
      })

      it('should show spinner', () => {
        // find spinner
        // expect that it exists
        // ASSERT
      })

      describe('when request times out', () => {
        beforeEach(() => {
          // run timers
          // ACT
        })

        it('should show error', () => {
          // find error
          // match text
          // ASSERT
        })
      })
    })

    describe('when api errors', () => {
      beforeEach(() => {
        // set mock to return unresolved promise
        // ARRANGE
        // action that calls into api
        // ACT
      })

      it('should show error', () => {
        // find error
        // match text
        // ASSERT
      })
    })
  })
  // #endregion
})

export const dummy = null

GifFinder

  • let’s test a component that uses state & effects to find gifs
import React, { FC, useEffect, useState } from 'react'
import classes from './example.module.css'
import { TenorAPI } from './tenor-api'

export interface GifFinderProps {
  onFound?(url: string): void
}

export const GifFinder: FC<GifFinderProps> = ({ onFound }) => {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<string[] | null>(null)

  useEffect(() => {
    let shouldUpdate = true

    if (query) {
      TenorAPI.search(query).then((newResults) => {
        if (shouldUpdate) setResults(newResults)
      })
    }

    return () => {
      shouldUpdate = false
    }
  }, [query])

  return (
    <section>
      <label>
        find a gif
        <input
          placeholder="query"
          value={query}
          onChange={(event) => setQuery(event.target.value)}
        />
      </label>
      {results && (
        <div className={classes.grid}>
          {results.map((result) => (
            <button
              type="button"
              key={result}
              className={classes.button}
              onClick={() => onFound && onFound(result)}
              aria-label="gif"
            >
              <img className={classes.image} src={result} alt="gif" />
            </button>
          ))}
        </div>
      )}
    </section>
  )
}

export const Example: FC = () => {
  const [gif, setGif] = useState<string | null>(null)

  return gif ? (
    <div>
      <img src={gif} alt="gif" />
      <p>found gif</p>
      <button type="button" onClick={() => setGif(null)}>
        find new gif
      </button>
    </div>
  ) : (
    <GifFinder onFound={setGif} />
  )
}

export default <Example />

Testing GifFinder

The more your tests resemble the way your software is used, the more confidence they can give you.

@testing-library

import {
  act,
  cleanup,
  fireEvent,
  render,
  RenderResult,
} from '@testing-library/react'
import React from 'react'
import { GifFinder } from './example'
import { TenorAPI } from './tenor-api'

describe('GifFinder', () => {
  let component: RenderResult
  let onFound: jest.Mock

  beforeEach(() => {
    onFound = jest.fn()
    component = render(<GifFinder onFound={onFound} />)
  })

  afterEach(() => {
    cleanup()
  })

  it('should snapshot', () => {
    expect(component.container).toMatchSnapshot()
  })

  describe('when searching', () => {
    let queryInput: HTMLElement
    let searchSpy: jest.SpyInstance

    beforeEach(async () => {
      const searchPromise = Promise.resolve(['gif1', 'gif2', 'gif3'])
      searchSpy = jest.spyOn(TenorAPI, 'search').mockReturnValue(searchPromise)
      queryInput = component.getByLabelText('find a gif')
      fireEvent.input(queryInput, { target: { value: 'react' } })
      await act(async () => {
        await searchPromise
      })
    })

    afterEach(() => {
      searchSpy.mockRestore()
    })

    it('should start a search', () => {
      expect(searchSpy).toHaveBeenCalledWith('react')
      expect(searchSpy).toHaveBeenCalledTimes(1)
    })

    it('should render the gifs', () => {
      expect(component.container).toMatchSnapshot()
    })

    describe('when clicking a gif', () => {
      beforeEach(() => {
        const [, gif2] = component.getAllByLabelText('gif')
        fireEvent.click(gif2)
      })

      it('should call onFound', () => {
        expect(onFound).toHaveBeenCalledWith('gif2')
        expect(onFound).toHaveBeenCalledTimes(1)
      })
    })
  })
})