~statianzo/angularjs-testing-library

b82fb936958a2b1c6bef7d84bc18da2269d93877 — Jason Staten 4 years ago 8b33a8f
feat: first passing render tests
5 files changed, 61 insertions(+), 169 deletions(-)

A jest.config.js
M package.json
M src/__tests__/__snapshots__/render.js.snap
M src/__tests__/render.js
M src/pure.js
A jest.config.js => jest.config.js +6 -0
@@ 0,0 1,6 @@
const config = require('kcd-scripts/jest')

module.exports = {
  ...config,
  testEnvironment: 'jsdom',
}

M package.json => package.json +2 -2
@@ 51,7 51,7 @@
  "devDependencies": {
    "@testing-library/jest-dom": "^4.1.0",
    "angular": "^1.7.8",
    "angular-mock": "^1.0.0",
    "angular-mocks": "^1.7.8",
    "cross-env": "^6.0.0",
    "kcd-scripts": "^1.7.0",
    "npm-run-all": "^4.1.5",


@@ 59,7 59,7 @@
  },
  "peerDependencies": {
    "angular": "*",
    "angular-mock": "*"
    "angular-mocks": "*"
  },
  "eslintConfig": {
    "extends": "./node_modules/kcd-scripts/eslint.js",

M src/__tests__/__snapshots__/render.js.snap => src/__tests__/__snapshots__/render.js.snap +17 -6
@@ 2,11 2,22 @@

exports[`supports fragments 1`] = `
<DocumentFragment>
  <div>
    <code>
      DocumentFragment
    </code>
     is pretty cool!
  </div>
  <atl-fragment
    class="ng-scope ng-isolate-scope"
  >
    
      
    <div>
      
        
      <code>
        DocumentFragment
      </code>
       is pretty cool!
      
    </div>
    
    
  </atl-fragment>
</DocumentFragment>
`;

M src/__tests__/render.js => src/__tests__/render.js +17 -75
@@ 1,89 1,31 @@
import React from 'react'
import ReactDOM from 'react-dom'
import angular from 'angular'
import 'angular-mocks'
import {render} from '../'

test('renders div into document', () => {
  const ref = React.createRef()
  const {container} = render(<div ref={ref} />)
  expect(container.firstChild).toBe(ref.current)
beforeEach(() => {
  angular.module('atl', [])
  angular.mock.module('atl')
})

test('works great with react portals', () => {
  class MyPortal extends React.Component {
    constructor(...args) {
      super(...args)
      this.portalNode = document.createElement('div')
      this.portalNode.dataset.testid = 'my-portal'
    }
    componentDidMount() {
      document.body.appendChild(this.portalNode)
    }
    componentWillUnmount() {
      this.portalNode.parentNode.removeChild(this.portalNode)
    }
    render() {
      return ReactDOM.createPortal(
        <Greet greeting="Hello" subject="World" />,
        this.portalNode,
      )
    }
  }

  function Greet({greeting, subject}) {
    return (
      <div>
        <strong>
          {greeting} {subject}
        </strong>
      </div>
    )
  }

  const {unmount, getByTestId, getByText} = render(<MyPortal />)
  expect(getByText('Hello World')).toBeInTheDocument()
  const portalNode = getByTestId('my-portal')
  expect(portalNode).toBeInTheDocument()
  unmount()
  expect(portalNode).not.toBeInTheDocument()
test('renders div into document', () => {
  const {container} = render(`<div id="child"></div>`)
  expect(container.firstChild.id).toBe('child')
})

test('returns baseElement which defaults to document.body', () => {
  const {baseElement} = render(<div />)
  const {baseElement} = render(`<div></div>`)
  expect(baseElement).toBe(document.body)
})

test('supports fragments', () => {
  class Test extends React.Component {
    render() {
      return (
        <div>
          <code>DocumentFragment</code> is pretty cool!
        </div>
      )
    }
  }

  const {asFragment} = render(<Test />)
  expect(asFragment()).toMatchSnapshot()
})

test('renders options.wrapper around node', () => {
  const WrapperComponent = ({children}) => (
    <div data-testid="wrapper">{children}</div>
  )

  const {container, getByTestId} = render(<div data-testid="inner" />, {
    wrapper: WrapperComponent,
  angular.module('atl').component('atlFragment', {
    template: `
      <div>
        <code>DocumentFragment</code> is pretty cool!
      </div>
    `,
  })

  expect(getByTestId('wrapper')).toBeInTheDocument()
  expect(container.firstChild).toMatchInlineSnapshot(`
<div
  data-testid="wrapper"
>
  <div
    data-testid="inner"
  />
</div>
`)
  const {asFragment} = render(`<atl-fragment></atl-fragment>`)
  expect(asFragment()).toMatchSnapshot()
})

M src/pure.js => src/pure.js +19 -86
@@ 1,35 1,14 @@
import React from 'react'
import ReactDOM from 'react-dom'
import angular from 'angular'
import 'angular-mocks'
import {
  getQueriesForElement,
  prettyDOM,
  fireEvent as dtlFireEvent,
  configure as configureDTL,
} from '@testing-library/dom'
import act, {asyncAct} from './act-compat'

configureDTL({
  asyncWrapper: async cb => {
    let result
    await asyncAct(async () => {
      result = await cb()
    })
    return result
  },
})

const mountedContainers = new Set()

function render(
  ui,
  {
    container,
    baseElement = container,
    queries,
    hydrate = false,
    wrapper: WrapperComponent,
  } = {},
) {
function render(ui, {container, baseElement = container, queries} = {}) {
  if (!baseElement) {
    // default to document.body instead of documentElement to avoid output of potentially-large
    // head elements (such as JSS style blocks) in debug output


@@ 44,18 23,19 @@ function render(
  // they're passing us a custom container or not.
  mountedContainers.add(container)

  const wrapUiIfNeeded = innerElement =>
    WrapperComponent
      ? React.createElement(WrapperComponent, null, innerElement)
      : innerElement
  let $scope
  angular.mock.inject([
    '$compile',
    '$rootScope',
    ($compile, $rootScope) => {
      $scope = $rootScope.$new()

  act(() => {
    if (hydrate) {
      ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
    } else {
      ReactDOM.render(wrapUiIfNeeded(ui), container)
    }
  })
      const element = $compile(ui)($scope)[0]
      container.appendChild(element)

      $scope.$digest()
    },
  ])

  return {
    container,


@@ 66,12 46,6 @@ function render(
          el.forEach(e => console.log(prettyDOM(e)))
        : // eslint-disable-next-line no-console,
          console.log(prettyDOM(el)),
    unmount: () => ReactDOM.unmountComponentAtNode(container),
    rerender: rerenderUi => {
      render(wrapUiIfNeeded(rerenderUi), {container, baseElement})
      // Intentionally do not return anything to avoid unnecessarily complicating the API.
      // folks can use all the same utilities we return in the first place that are bound to the container
    },
    asFragment: () => {
      /* istanbul ignore if (jsdom limitation) */
      if (typeof document.createRange === 'function') {


@@ 84,6 58,7 @@ function render(
      template.innerHTML = container.innerHTML
      return template.content
    },
    $scope,
    ...getQueriesForElement(baseElement, queries),
  }
}


@@ 95,63 70,21 @@ function cleanup() {
// maybe one day we'll expose this (perhaps even as a utility returned by render).
// but let's wait until someone asks for it.
function cleanupAtContainer(container) {
  ReactDOM.unmountComponentAtNode(container)
  if (container.parentNode === document.body) {
    document.body.removeChild(container)
  }
  mountedContainers.delete(container)
}

// react-testing-library's version of fireEvent will call
// dom-testing-library's version of fireEvent wrapped inside
// an "act" call so that after all event callbacks have been
// been called, the resulting useEffect callbacks will also
// be called.
function fireEvent(...args) {
  let returnValue
  act(() => {
    returnValue = dtlFireEvent(...args)
  })
  return returnValue
  return dtlFireEvent(...args)
}

Object.keys(dtlFireEvent).forEach(key => {
  fireEvent[key] = (...args) => {
    let returnValue
    act(() => {
      returnValue = dtlFireEvent[key](...args)
    })
    return returnValue
    return dtlFireEvent[key](...args)
  }
})

// React event system tracks native mouseOver/mouseOut events for
// running onMouseEnter/onMouseLeave handlers
// @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/EnterLeaveEventPlugin.js#L24-L31
fireEvent.mouseEnter = fireEvent.mouseOver
fireEvent.mouseLeave = fireEvent.mouseOut

fireEvent.select = (node, init) => {
  // React tracks this event only on focused inputs
  node.focus()

  // React creates this event when one of the following native events happens
  // - contextMenu
  // - mouseUp
  // - dragEnd
  // - keyUp
  // - keyDown
  // so we can use any here
  // @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/SelectEventPlugin.js#L203-L224
  fireEvent.keyUp(node, init)
}

// just re-export everything from dom-testing-library
export * from '@testing-library/dom'
export {render, cleanup, fireEvent, act}

// NOTE: we're not going to export asyncAct because that's our own compatibility
// thing for people using react-dom@16.8.0. Anyone else doesn't need it and
// people should just upgrade anyway.

/* eslint func-name-matching:0 */
export {render, cleanup, fireEvent}