feat: wait flushes angular tasks

ts
Jason Staten 5 years ago
parent ace9f5bdbf
commit 1706665619

@ -1,39 +1,60 @@
import React from 'react' import angular from 'angular'
import 'angular-mocks'
import {render, wait} from '../' import {render, wait} from '../'
const fetchAMessage = () => class controller {
new Promise(resolve => { loading = true
// we are using random timeout here to simulate a real-time example data = null
// of an async operation calling a callback at a non-deterministic time
const randomTimeout = Math.floor(Math.random() * 100) constructor($q, $timeout) {
setTimeout(() => { this.$q = $q
resolve({returnedMessage: 'Hello World'}) this.$timeout = $timeout
}, randomTimeout) }
})
class ComponentWithLoader extends React.Component { load() {
state = {loading: true} return this.$q(resolve => {
async componentDidMount() { // we are using random timeout here to simulate a real-time example
const data = await fetchAMessage() // of an async operation calling a callback at a non-deterministic time
this.setState({data, loading: false}) // eslint-disable-line const randomTimeout = Math.floor(Math.random() * 100)
this.$timeout(() => {
resolve({returnedMessage: 'Hello World'})
}, randomTimeout)
})
} }
render() {
if (this.state.loading) { $onInit() {
return <div>Loading...</div> this.load().then(response => {
} this.loading = false
return ( this.data = response
<div data-testid="message"> })
Loaded this message: {this.state.data.returnedMessage}!
</div>
)
} }
} }
const template = `
<div ng-if="$ctrl.loading">Loading...</div>
<div
ng-if="!$ctrl.loading"
data-testid="message"
>
Loaded this message: {{$ctrl.data.returnedMessage}}!
</div>
`
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 () => { test('it waits for the data to be loaded', async () => {
const {queryByText, queryByTestId} = render(<ComponentWithLoader />) const {queryByText, findByTestId} = render(`<atl-end></atl-end>`)
expect(queryByText('Loading...')).toBeTruthy() expect(queryByText('Loading...')).toBeTruthy()
await wait(() => expect(queryByText('Loading...')).toBeNull()) await wait(() => expect(queryByText('Loading...')).toBeNull())
expect(queryByTestId('message').textContent).toMatch(/Hello World/) const message = await findByTestId('message')
expect(message.textContent).toMatch(/Hello World/)
}) })

@ -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)
},
}
}

@ -1,5 +1,4 @@
import flush from './flush-microtasks' import {flush, cleanup} from './pure'
import {cleanup} from './pure'
// if we're running in a test runner that supports afterEach // if we're running in a test runner that supports afterEach
// then we'll automatically run cleanup afterEach test // 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 // if you don't like this then either import the `pure` module
// or set the ATL_SKIP_AUTO_CLEANUP env variable to 'true'. // or set the ATL_SKIP_AUTO_CLEANUP env variable to 'true'.
if (typeof afterEach === 'function' && !process.env.ATL_SKIP_AUTO_CLEANUP) { if (typeof afterEach === 'function' && !process.env.ATL_SKIP_AUTO_CLEANUP) {
afterEach(async () => { afterEach(() => {
await flush() flush()
cleanup() cleanup()
}) })
} }

@ -4,6 +4,7 @@ import {
getQueriesForElement, getQueriesForElement,
prettyDOM, prettyDOM,
fireEvent as dtlFireEvent, fireEvent as dtlFireEvent,
wait as dtlWait,
} from '@testing-library/dom' } from '@testing-library/dom'
const mountedContainers = new Set() 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. // they're passing us a custom container or not.
mountedContainers.add(container) mountedContainers.add(container)
let $scope const $rootScope = getAngularService('$rootScope')
angular.mock.inject([ const $compile = getAngularService('$compile')
'$compile', const $scope = $rootScope.$new()
'$rootScope', Object.assign($scope, scope)
($compile, $rootScope) => {
$scope = $rootScope.$new()
mountedScopes.add($scope)
Object.assign($scope, scope)
const element = $compile(ui)($scope)[0] mountedScopes.add($scope)
container.appendChild(element)
$scope.$digest() const element = $compile(ui)($scope)[0]
}, container.appendChild(element)
])
$scope.$digest()
return { return {
container, container,
@ -84,19 +81,19 @@ function cleanupScope(scope) {
scope.$destroy() scope.$destroy()
} }
function getRootScope() { function getAngularService(name) {
let $rootScope let service
angular.mock.inject([ angular.mock.inject([
'$rootScope', name,
rootScope => { injected => {
$rootScope = rootScope service = injected
}, },
]) ])
return $rootScope return service
} }
function fireEvent(...args) { function fireEvent(...args) {
const $rootScope = getRootScope() const $rootScope = getAngularService('$rootScope')
const result = dtlFireEvent(...args) const result = dtlFireEvent(...args)
$rootScope.$digest() $rootScope.$digest()
return result return result
@ -104,7 +101,7 @@ function fireEvent(...args) {
Object.keys(dtlFireEvent).forEach(key => { Object.keys(dtlFireEvent).forEach(key => {
fireEvent[key] = (...args) => { fireEvent[key] = (...args) => {
const $rootScope = getRootScope() const $rootScope = getAngularService('$rootScope')
const result = dtlFireEvent[key](...args) const result = dtlFireEvent[key](...args)
$rootScope.$digest() $rootScope.$digest()
return result return result
@ -115,5 +112,21 @@ Object.keys(dtlFireEvent).forEach(key => {
fireEvent.mouseEnter = fireEvent.mouseOver fireEvent.mouseEnter = fireEvent.mouseOver
fireEvent.mouseLeave = fireEvent.mouseOut 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 * from '@testing-library/dom'
export {render, cleanup, fireEvent} export {render, cleanup, fireEvent, wait, flush}

Loading…
Cancel
Save