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 '../'
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 <div>Loading...</div>
}
return (
<div data-testid="message">
Loaded this message: {this.state.data.returnedMessage}!
</div>
)
$onInit() {
this.load().then(response => {
this.loading = false
this.data = response
})
}
}
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 () => {
const {queryByText, queryByTestId} = render(<ComponentWithLoader />)
const {queryByText, findByTestId} = render(`<atl-end></atl-end>`)
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/)
})

@ -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 {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()
})
}

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

Loading…
Cancel
Save