From 170666561972531f131342858cb93674845c663e Mon Sep 17 00:00:00 2001 From: Jason Staten Date: Sat, 2 Nov 2019 20:29:25 -0600 Subject: [PATCH] feat: wait flushes angular tasks --- src/__tests__/end-to-end.js | 73 ++++++++++++++++++++++++------------- src/flush-microtasks.js | 47 ------------------------ src/index.js | 7 ++-- src/pure.js | 57 ++++++++++++++++++----------- 4 files changed, 85 insertions(+), 99 deletions(-) delete mode 100644 src/flush-microtasks.js diff --git a/src/__tests__/end-to-end.js b/src/__tests__/end-to-end.js index 4cdfc32..457f097 100644 --- a/src/__tests__/end-to-end.js +++ b/src/__tests__/end-to-end.js @@ -1,39 +1,60 @@ -import React from 'react' +import angular from 'angular' +import 'angular-mocks' import {render, wait} from '../' -const fetchAMessage = () => - new Promise(resolve => { - // we are using random timeout here to simulate a real-time example - // of an async operation calling a callback at a non-deterministic time - const randomTimeout = Math.floor(Math.random() * 100) - setTimeout(() => { - resolve({returnedMessage: 'Hello World'}) - }, randomTimeout) - }) +class controller { + loading = true + data = null + + constructor($q, $timeout) { + this.$q = $q + this.$timeout = $timeout + } -class ComponentWithLoader extends React.Component { - state = {loading: true} - async componentDidMount() { - const data = await fetchAMessage() - this.setState({data, loading: false}) // eslint-disable-line + load() { + return this.$q(resolve => { + // we are using random timeout here to simulate a real-time example + // of an async operation calling a callback at a non-deterministic time + const randomTimeout = Math.floor(Math.random() * 100) + this.$timeout(() => { + resolve({returnedMessage: 'Hello World'}) + }, randomTimeout) + }) } - render() { - if (this.state.loading) { - return
Loading...
- } - return ( -
- Loaded this message: {this.state.data.returnedMessage}! -
- ) + + $onInit() { + this.load().then(response => { + this.loading = false + this.data = response + }) } } +const template = ` +
Loading...
+
+ Loaded this message: {{$ctrl.data.returnedMessage}}! +
+` + +beforeEach(() => { + angular.module('atl', []) + angular.mock.module('atl') + angular.module('atl').component('atlEnd', { + template, + controller, + }) +}) + test('it waits for the data to be loaded', async () => { - const {queryByText, queryByTestId} = render() + const {queryByText, findByTestId} = render(``) expect(queryByText('Loading...')).toBeTruthy() await wait(() => expect(queryByText('Loading...')).toBeNull()) - expect(queryByTestId('message').textContent).toMatch(/Hello World/) + const message = await findByTestId('message') + expect(message.textContent).toMatch(/Hello World/) }) diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js deleted file mode 100644 index a34b5e8..0000000 --- a/src/flush-microtasks.js +++ /dev/null @@ -1,47 +0,0 @@ -/* istanbul ignore file */ -// the part of this file that we need tested is definitely being run -// and the part that is not cannot easily have useful tests written -// anyway. So we're just going to ignore coverage for this file -/** - * copied from React's enqueueTask.js - */ - -let didWarnAboutMessageChannel = false -let enqueueTask -try { - // read require off the module object to get around the bundlers. - // we don't want them to detect a require and bundle a Node polyfill. - const requireString = `require${Math.random()}`.slice(0, 7) - const nodeRequire = module && module[requireString] - // assuming we're in node, let's try to get node's - // version of setImmediate, bypassing fake timers if any. - enqueueTask = nodeRequire('timers').setImmediate -} catch (_err) { - // we're in a browser - // we can't use regular timers because they may still be faked - // so we try MessageChannel+postMessage instead - enqueueTask = callback => { - if (didWarnAboutMessageChannel === false) { - didWarnAboutMessageChannel = true - // eslint-disable-next-line no-console - console.error( - typeof MessageChannel !== 'undefined', - 'This browser does not have a MessageChannel implementation, ' + - 'so enqueuing tasks via await act(async () => ...) will fail. ' + - 'Please file an issue at https://github.com/facebook/react/issues ' + - 'if you encounter this warning.', - ) - } - const channel = new MessageChannel() - channel.port1.onmessage = callback - channel.port2.postMessage(undefined) - } -} - -export default function flushMicroTasks() { - return { - then(resolve) { - enqueueTask(resolve) - }, - } -} diff --git a/src/index.js b/src/index.js index 8755c02..30bc039 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ -import flush from './flush-microtasks' -import {cleanup} from './pure' +import {flush, cleanup} from './pure' // if we're running in a test runner that supports afterEach // then we'll automatically run cleanup afterEach test @@ -7,8 +6,8 @@ import {cleanup} from './pure' // if you don't like this then either import the `pure` module // or set the ATL_SKIP_AUTO_CLEANUP env variable to 'true'. if (typeof afterEach === 'function' && !process.env.ATL_SKIP_AUTO_CLEANUP) { - afterEach(async () => { - await flush() + afterEach(() => { + flush() cleanup() }) } diff --git a/src/pure.js b/src/pure.js index 6551230..b91c578 100644 --- a/src/pure.js +++ b/src/pure.js @@ -4,6 +4,7 @@ import { getQueriesForElement, prettyDOM, fireEvent as dtlFireEvent, + wait as dtlWait, } from '@testing-library/dom' const mountedContainers = new Set() @@ -24,21 +25,17 @@ function render(ui, {container, baseElement = container, queries, scope} = {}) { // they're passing us a custom container or not. mountedContainers.add(container) - let $scope - angular.mock.inject([ - '$compile', - '$rootScope', - ($compile, $rootScope) => { - $scope = $rootScope.$new() - mountedScopes.add($scope) - Object.assign($scope, scope) + const $rootScope = getAngularService('$rootScope') + const $compile = getAngularService('$compile') + const $scope = $rootScope.$new() + Object.assign($scope, scope) - const element = $compile(ui)($scope)[0] - container.appendChild(element) + mountedScopes.add($scope) - $scope.$digest() - }, - ]) + const element = $compile(ui)($scope)[0] + container.appendChild(element) + + $scope.$digest() return { container, @@ -84,19 +81,19 @@ function cleanupScope(scope) { scope.$destroy() } -function getRootScope() { - let $rootScope +function getAngularService(name) { + let service angular.mock.inject([ - '$rootScope', - rootScope => { - $rootScope = rootScope + name, + injected => { + service = injected }, ]) - return $rootScope + return service } function fireEvent(...args) { - const $rootScope = getRootScope() + const $rootScope = getAngularService('$rootScope') const result = dtlFireEvent(...args) $rootScope.$digest() return result @@ -104,7 +101,7 @@ function fireEvent(...args) { Object.keys(dtlFireEvent).forEach(key => { fireEvent[key] = (...args) => { - const $rootScope = getRootScope() + const $rootScope = getAngularService('$rootScope') const result = dtlFireEvent[key](...args) $rootScope.$digest() return result @@ -115,5 +112,21 @@ Object.keys(dtlFireEvent).forEach(key => { fireEvent.mouseEnter = fireEvent.mouseOver fireEvent.mouseLeave = fireEvent.mouseOut +function flush() { + const $browser = getAngularService('$browser') + if ($browser.deferredFns.length) { + $browser.defer.flush() + } +} + +function wait(callback, options) { + dtlWait(() => { + flush() + if (callback) { + callback() + } + }, options) +} + export * from '@testing-library/dom' -export {render, cleanup, fireEvent} +export {render, cleanup, fireEvent, wait, flush}