From ace9f5bdbf166424d127e8aa09c09c62bacd77bc Mon Sep 17 00:00:00 2001 From: Jason Staten Date: Fri, 1 Nov 2019 22:56:38 -0600 Subject: [PATCH] feat(events): fireEvent triggers a digest --- src/__tests__/digest.js | 39 ++++++++++++ src/__tests__/events.js | 18 +++--- src/act-compat.js | 135 ---------------------------------------- src/pure.js | 31 ++++++--- 4 files changed, 68 insertions(+), 155 deletions(-) create mode 100644 src/__tests__/digest.js delete mode 100644 src/act-compat.js diff --git a/src/__tests__/digest.js b/src/__tests__/digest.js new file mode 100644 index 0000000..3b3e793 --- /dev/null +++ b/src/__tests__/digest.js @@ -0,0 +1,39 @@ +import angular from 'angular' +import 'angular-mocks' +import {render, fireEvent} from '../' + +beforeEach(() => { + angular.module('atl', []) + angular.mock.module('atl') +}) + +test('`fireEvent` triggers a digest', () => { + angular.module('atl').component('atlDigest', { + template: ` + +
+ Clicked! +
+ `, + controller: class { + wasClicked = false + btn = null + $postLink() { + this.btn.on('click', this.handleClick) + } + + handleClick = () => { + this.wasClicked = true + } + }, + }) + + const {getByRole, queryByText} = render(``) + + const button = getByRole('button') + expect(queryByText('Clicked!')).toBeNull() + fireEvent.click(button) + expect(queryByText('Clicked!')).not.toBeNull() +}) diff --git a/src/__tests__/events.js b/src/__tests__/events.js index 8fb8485..808494d 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -135,10 +135,8 @@ eventTypes.forEach(({type, events, elementType, init}) => { it(`triggers ${eventName}`, () => { const spy = jest.fn() - const {getByTestId} = render( - ` - <${elementType} - data-testid="target" + const {container} = render( + `<${elementType} ng-on-${propName}="spy()" >`, { @@ -148,8 +146,7 @@ eventTypes.forEach(({type, events, elementType, init}) => { }, ) - const target = getByTestId('target') - fireEvent[eventName](target, init) + fireEvent[eventName](container.firstChild, init) expect(spy).toHaveBeenCalledTimes(1) }) }) @@ -160,11 +157,10 @@ test('calling `fireEvent` directly works too', () => { const spy = jest.fn() const {getByTestId} = render( - ` - `, + ``, { scope: { spy, diff --git a/src/act-compat.js b/src/act-compat.js deleted file mode 100644 index d758a97..0000000 --- a/src/act-compat.js +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import * as testUtils from 'react-dom/test-utils' - -const reactAct = testUtils.act -const actSupported = reactAct !== undefined - -// 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(
, document.createElement('div')) -} - -const act = reactAct || actPolyfill - -let youHaveBeenWarned = false -let isAsyncActSupported = null - -function asyncAct(cb) { - if (actSupported === true) { - if (isAsyncActSupported === null) { - return new Promise((resolve, reject) => { - // patch console.error here - const originalConsoleError = console.error - console.error = function error(...args) { - /* if console.error fired *with that specific message* */ - /* istanbul ignore next */ - const firstArgIsString = typeof args[0] === 'string' - if ( - firstArgIsString && - args[0].indexOf( - 'Warning: Do not await the result of calling ReactTestUtils.act', - ) === 0 - ) { - // v16.8.6 - isAsyncActSupported = false - } else if ( - firstArgIsString && - args[0].indexOf( - 'Warning: The callback passed to ReactTestUtils.act(...) function must not return anything', - ) === 0 - ) { - // no-op - } else { - originalConsoleError.apply(console, args) - } - } - let cbReturn, result - try { - result = reactAct(() => { - cbReturn = cb() - return cbReturn - }) - } catch (err) { - console.error = originalConsoleError - reject(err) - return - } - - result.then( - () => { - console.error = originalConsoleError - // if it got here, it means async act is supported - isAsyncActSupported = true - resolve() - }, - err => { - console.error = originalConsoleError - isAsyncActSupported = true - reject(err) - }, - ) - - // 16.8.6's act().then() doesn't call a resolve handler, so we need to manually flush here, sigh - - if (isAsyncActSupported === false) { - console.error = originalConsoleError - /* istanbul ignore next */ - if (!youHaveBeenWarned) { - // 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 - } - - cbReturn.then(() => { - // a faux-version. - // todo - copy https://github.com/facebook/react/blob/master/packages/shared/enqueueTask.js - Promise.resolve().then(() => { - // use sync act to flush effects - act(() => {}) - resolve() - }) - }, reject) - } - }) - } else if (isAsyncActSupported === false) { - // use the polyfill directly - let result - act(() => { - result = cb() - }) - return result.then(() => { - return Promise.resolve().then(() => { - // use sync act to flush effects - act(() => {}) - }) - }) - } - // all good! regular act - return act(cb) - } - // use the polyfill - let result - act(() => { - result = cb() - }) - return result.then(() => { - return Promise.resolve().then(() => { - // use sync act to flush effects - act(() => {}) - }) - }) -} - -export default act -export {asyncAct} - -/* eslint no-console:0 */ diff --git a/src/pure.js b/src/pure.js index 8e241b2..6551230 100644 --- a/src/pure.js +++ b/src/pure.js @@ -84,23 +84,36 @@ function cleanupScope(scope) { scope.$destroy() } +function getRootScope() { + let $rootScope + angular.mock.inject([ + '$rootScope', + rootScope => { + $rootScope = rootScope + }, + ]) + return $rootScope +} + function fireEvent(...args) { - return dtlFireEvent(...args) + const $rootScope = getRootScope() + const result = dtlFireEvent(...args) + $rootScope.$digest() + return result } Object.keys(dtlFireEvent).forEach(key => { fireEvent[key] = (...args) => { - return dtlFireEvent[key](...args) + const $rootScope = getRootScope() + const result = dtlFireEvent[key](...args) + $rootScope.$digest() + return result } }) -fireEvent.mouseEnter = (...args) => { - return dtlFireEvent.mouseOver(...args) -} - -fireEvent.mouseLeave = (...args) => { - return dtlFireEvent.mouseOut(...args) -} +// AngularJS maps `mouseEnter` to `mouseOver` and `mouseLeave` to `mouseOut` +fireEvent.mouseEnter = fireEvent.mouseOver +fireEvent.mouseLeave = fireEvent.mouseOut export * from '@testing-library/dom' export {render, cleanup, fireEvent} -- 2.45.2