UNPKG

react-testing-library

Version:

Simple and complete React DOM testing utilities that encourage good testing practices.

306 lines (255 loc) 10.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var _extends = _interopDefault(require('@babel/runtime/helpers/extends')); var _regeneratorRuntime = _interopDefault(require('@babel/runtime/regenerator')); var _asyncToGenerator = _interopDefault(require('@babel/runtime/helpers/asyncToGenerator')); var React = _interopDefault(require('react')); var ReactDOM = _interopDefault(require('react-dom')); var domTestingLibrary = require('dom-testing-library'); // we don't want to warn until react-dom@16.9.0 is actually released var reactDomSixteenPointNineIsReleased = false; var reactAct; var actSupported = false; var asyncActSupported = false; try { reactAct = require('react-dom/test-utils').act; actSupported = reactAct !== undefined; var originalError = console.error; var errorCalled = false; console.error = function () { errorCalled = true; }; console.error.calls = []; /* istanbul ignore next */ reactAct(function () { return { then: function then() {} }; }).then(function () {}); /* istanbul ignore next */ if (!errorCalled) { asyncActSupported = true; } console.error = originalError; } catch (error) {} // ignore, this is to support old versions of react // act is supported react-dom@16.8.0 // so for versions that don't have act from test utils // we do this little polyfill. No warnings, but it's // better than nothing. function actPolyfill(cb) { ReactDOM.unstable_batchedUpdates(cb); ReactDOM.render(React.createElement("div", null), document.createElement('div')); } var act = reactAct || actPolyfill; var youHaveBeenWarned = false; // this will not avoid warnings that react-dom 16.8.0 logs for triggering // state updates asynchronously, but at least we can tell people they need // to upgrade to avoid the warnings. function asyncActPolyfill() { return _asyncActPolyfill.apply(this, arguments); } // istanbul ignore next function _asyncActPolyfill() { _asyncActPolyfill = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee(cb) { return _regeneratorRuntime.wrap(function (_context) { while (1) { switch (_context.prev = _context.next) { case 0: // istanbul-ignore-next if (!youHaveBeenWarned && actSupported && reactDomSixteenPointNineIsReleased) { // if act is supported and async act isn't and they're trying to use async // act, then they need to upgrade from 16.8 to 16.9. // This is a seemless upgrade, so we'll add a warning console.error("It looks like you're using a version of react-dom that supports the \"act\" function, but not an awaitable version of \"act\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning."); youHaveBeenWarned = true; } _context.next = 3; return cb(); case 3: // make all effects resolve after act(function () {}); case 4: case "end": return _context.stop(); } } }, _callee); })); return _asyncActPolyfill.apply(this, arguments); } var asyncAct = asyncActSupported ? reactAct : asyncActPolyfill; /* eslint no-console:0 */ domTestingLibrary.configure({ asyncWrapper: function () { var _asyncWrapper = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee2(cb) { var result; return _regeneratorRuntime.wrap(function (_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return asyncAct( /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee() { return _regeneratorRuntime.wrap(function (_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return cb(); case 2: result = _context.sent; case 3: case "end": return _context.stop(); } } }, _callee); }))); case 2: return _context2.abrupt("return", result); case 3: case "end": return _context2.stop(); } } }, _callee2); })); return function asyncWrapper() { return _asyncWrapper.apply(this, arguments); }; }() }); var mountedContainers = new Set(); function render(ui, _temp) { var _ref2 = _temp === void 0 ? {} : _temp, container = _ref2.container, _ref2$baseElement = _ref2.baseElement, baseElement = _ref2$baseElement === void 0 ? container : _ref2$baseElement, queries = _ref2.queries, _ref2$hydrate = _ref2.hydrate, hydrate = _ref2$hydrate === void 0 ? false : _ref2$hydrate, WrapperComponent = _ref2.wrapper; if (!container) { // default to document.body instead of documentElement to avoid output of potentially-large // head elements (such as JSS style blocks) in debug output baseElement = document.body; container = document.body.appendChild(document.createElement('div')); } // we'll add it to the mounted containers regardless of whether it's actually // added to document.body so the cleanup method works regardless of whether // they're passing us a custom container or not. mountedContainers.add(container); var wrapUiIfNeeded = function (innerElement) { return WrapperComponent ? React.createElement(WrapperComponent, null, innerElement) : innerElement; }; act(function () { if (hydrate) { ReactDOM.hydrate(wrapUiIfNeeded(ui), container); } else { ReactDOM.render(wrapUiIfNeeded(ui), container); } }); return _extends({ container: container, baseElement: baseElement, // eslint-disable-next-line no-console debug: function debug(el) { if (el === void 0) { el = baseElement; } return console.log(domTestingLibrary.prettyDOM(el)); }, unmount: function unmount() { return ReactDOM.unmountComponentAtNode(container); }, rerender: function rerender(rerenderUi) { render(wrapUiIfNeeded(rerenderUi), { container: container, baseElement: 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: function asFragment() { /* istanbul ignore if (jsdom limitation) */ if (typeof document.createRange === 'function') { return document.createRange().createContextualFragment(container.innerHTML); } var template = document.createElement('template'); template.innerHTML = container.innerHTML; return template.content; } }, domTestingLibrary.getQueriesForElement(baseElement, queries)); } function cleanup() { mountedContainers.forEach(cleanupAtContainer); } // 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() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var returnValue; act(function () { returnValue = domTestingLibrary.fireEvent.apply(void 0, args); }); return returnValue; } Object.keys(domTestingLibrary.fireEvent).forEach(function (key) { fireEvent[key] = function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } var returnValue; act(function () { returnValue = domTestingLibrary.fireEvent[key].apply(domTestingLibrary.fireEvent, 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 = function (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 // 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 */ Object.keys(domTestingLibrary).forEach(function (key) { Object.defineProperty(exports, key, { enumerable: true, get: function () { return domTestingLibrary[key]; } }); }); exports.act = act; exports.cleanup = cleanup; exports.fireEvent = fireEvent; exports.render = render;