feat: first passing render tests

ts
Jason Staten 5 years ago
parent 8b33a8f7c9
commit b82fb93695

@ -0,0 +1,6 @@
const config = require('kcd-scripts/jest')
module.exports = {
...config,
testEnvironment: 'jsdom',
}

@ -51,7 +51,7 @@
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^4.1.0", "@testing-library/jest-dom": "^4.1.0",
"angular": "^1.7.8", "angular": "^1.7.8",
"angular-mock": "^1.0.0", "angular-mocks": "^1.7.8",
"cross-env": "^6.0.0", "cross-env": "^6.0.0",
"kcd-scripts": "^1.7.0", "kcd-scripts": "^1.7.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
@ -59,7 +59,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"angular": "*", "angular": "*",
"angular-mock": "*" "angular-mocks": "*"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js", "extends": "./node_modules/kcd-scripts/eslint.js",

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

@ -1,89 +1,31 @@
import React from 'react' import angular from 'angular'
import ReactDOM from 'react-dom' import 'angular-mocks'
import {render} from '../' import {render} from '../'
test('renders div into document', () => { beforeEach(() => {
const ref = React.createRef() angular.module('atl', [])
const {container} = render(<div ref={ref} />) angular.mock.module('atl')
expect(container.firstChild).toBe(ref.current)
}) })
test('works great with react portals', () => { test('renders div into document', () => {
class MyPortal extends React.Component { const {container} = render(`<div id="child"></div>`)
constructor(...args) { expect(container.firstChild.id).toBe('child')
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('returns baseElement which defaults to document.body', () => { test('returns baseElement which defaults to document.body', () => {
const {baseElement} = render(<div />) const {baseElement} = render(`<div></div>`)
expect(baseElement).toBe(document.body) expect(baseElement).toBe(document.body)
}) })
test('supports fragments', () => { test('supports fragments', () => {
class Test extends React.Component { angular.module('atl').component('atlFragment', {
render() { template: `
return ( <div>
<div> <code>DocumentFragment</code> is pretty cool!
<code>DocumentFragment</code> is pretty cool! </div>
</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,
}) })
expect(getByTestId('wrapper')).toBeInTheDocument() const {asFragment} = render(`<atl-fragment></atl-fragment>`)
expect(container.firstChild).toMatchInlineSnapshot(` expect(asFragment()).toMatchSnapshot()
<div
data-testid="wrapper"
>
<div
data-testid="inner"
/>
</div>
`)
}) })

@ -1,35 +1,14 @@
import React from 'react' import angular from 'angular'
import ReactDOM from 'react-dom' import 'angular-mocks'
import { import {
getQueriesForElement, getQueriesForElement,
prettyDOM, prettyDOM,
fireEvent as dtlFireEvent, fireEvent as dtlFireEvent,
configure as configureDTL,
} from '@testing-library/dom' } 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() const mountedContainers = new Set()
function render( function render(ui, {container, baseElement = container, queries} = {}) {
ui,
{
container,
baseElement = container,
queries,
hydrate = false,
wrapper: WrapperComponent,
} = {},
) {
if (!baseElement) { if (!baseElement) {
// default to document.body instead of documentElement to avoid output of potentially-large // default to document.body instead of documentElement to avoid output of potentially-large
// head elements (such as JSS style blocks) in debug output // 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. // they're passing us a custom container or not.
mountedContainers.add(container) mountedContainers.add(container)
const wrapUiIfNeeded = innerElement => let $scope
WrapperComponent angular.mock.inject([
? React.createElement(WrapperComponent, null, innerElement) '$compile',
: innerElement '$rootScope',
($compile, $rootScope) => {
$scope = $rootScope.$new()
act(() => { const element = $compile(ui)($scope)[0]
if (hydrate) { container.appendChild(element)
ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
} else { $scope.$digest()
ReactDOM.render(wrapUiIfNeeded(ui), container) },
} ])
})
return { return {
container, container,
@ -66,12 +46,6 @@ function render(
el.forEach(e => console.log(prettyDOM(e))) el.forEach(e => console.log(prettyDOM(e)))
: // eslint-disable-next-line no-console, : // eslint-disable-next-line no-console,
console.log(prettyDOM(el)), 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: () => { asFragment: () => {
/* istanbul ignore if (jsdom limitation) */ /* istanbul ignore if (jsdom limitation) */
if (typeof document.createRange === 'function') { if (typeof document.createRange === 'function') {
@ -84,6 +58,7 @@ function render(
template.innerHTML = container.innerHTML template.innerHTML = container.innerHTML
return template.content return template.content
}, },
$scope,
...getQueriesForElement(baseElement, queries), ...getQueriesForElement(baseElement, queries),
} }
} }
@ -95,63 +70,21 @@ function cleanup() {
// maybe one day we'll expose this (perhaps even as a utility returned by render). // maybe one day we'll expose this (perhaps even as a utility returned by render).
// but let's wait until someone asks for it. // but let's wait until someone asks for it.
function cleanupAtContainer(container) { function cleanupAtContainer(container) {
ReactDOM.unmountComponentAtNode(container)
if (container.parentNode === document.body) { if (container.parentNode === document.body) {
document.body.removeChild(container) document.body.removeChild(container)
} }
mountedContainers.delete(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) { function fireEvent(...args) {
let returnValue return dtlFireEvent(...args)
act(() => {
returnValue = dtlFireEvent(...args)
})
return returnValue
} }
Object.keys(dtlFireEvent).forEach(key => { Object.keys(dtlFireEvent).forEach(key => {
fireEvent[key] = (...args) => { fireEvent[key] = (...args) => {
let returnValue return dtlFireEvent[key](...args)
act(() => {
returnValue = dtlFireEvent[key](...args)
})
return returnValue
} }
}) })
// 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 * from '@testing-library/dom'
export {render, cleanup, fireEvent, act} export {render, cleanup, fireEvent}
// 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 */

Loading…
Cancel
Save