-
Notifications
You must be signed in to change notification settings - Fork 470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wait for element to be removed #218
Changes from 10 commits
f3eca1c
c547962
cc21522
c75e492
a349725
d1d303d
39fea0e
36c4e4c
f5b081d
2dedb1f
54cbc89
999ff5c
e7a018e
0518e78
d6150af
4e2f81c
20ada81
46a952f
618c240
af327d1
505d783
6fcfc83
579c876
2f7452d
20cd110
bc2e3f2
9834de2
17f136f
6664f0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`it returns error immediately if there callback returns falsy value or error before any mutations 1`] = ` | ||
Array [ | ||
[ReferenceError: timer is not defined], | ||
] | ||
`; | ||
|
||
exports[`it returns error immediately if there callback returns falsy value or error before any mutations 2`] = ` | ||
Array [ | ||
[ReferenceError: timer is not defined], | ||
Tolsee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
] | ||
`; | ||
|
||
exports[`it throws if timeout is exceeded 1`] = ` | ||
Array [ | ||
[Error: Timed out in waitForElementToBeRemoved.], | ||
] | ||
`; | ||
|
||
exports[`it waits characterData mutation 1`] = ` | ||
Array [ | ||
true, | ||
] | ||
`; | ||
|
||
exports[`it waits for the attributes mutation 1`] = ` | ||
Array [ | ||
true, | ||
] | ||
`; | ||
|
||
exports[`it waits for the callback to throw error or a falsy value and only reacts to DOM mutations 1`] = ` | ||
Array [ | ||
true, | ||
] | ||
`; | ||
|
||
exports[`throws an error if callback is not a function 1`] = ` | ||
Array [ | ||
"waitForElementToBeRemoved requires a callback as the first parameter", | ||
] | ||
`; | ||
|
||
exports[`works if a container is not defined 1`] = ` | ||
Array [ | ||
true, | ||
] | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
import {waitForElementToBeRemoved, wait} from '../' | ||
// adds special assertions like toBeTruthy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. toBeTruthy is built-into jest. This adds things like toBeInTheDocument. But I don't think we need this comment anyway. Please remove it. |
||
import 'jest-dom/extend-expect' | ||
import {render} from './helpers/test-utils' | ||
import document from './helpers/document' | ||
|
||
jest.useFakeTimers() | ||
|
||
const skipSomeTimeForMutationObserver = (delayMs = 50) => { | ||
jest.advanceTimersByTime(delayMs) | ||
jest.runAllImmediates() | ||
} | ||
|
||
test('it waits for the callback to throw error or a falsy value and only reacts to DOM mutations', async () => { | ||
const {container, getByTestId} = render( | ||
`<div data-testid="initial-element"> | ||
</div>`, | ||
) | ||
|
||
const testEle = render( | ||
`<div data-testid="the-element-we-are-looking-for"></div>`, | ||
).container.firstChild | ||
testEle.parentNode.removeChild(testEle) | ||
container.appendChild(testEle) | ||
|
||
let nextElIndex = 0 | ||
const makeMutationFn = () => () => { | ||
container.appendChild( | ||
render( | ||
`<div data-testid="another-element-that-causes-mutation-${++nextElIndex}"></div>`, | ||
).container.firstChild, | ||
) | ||
} | ||
|
||
const mutationsAndCallbacks = [ | ||
[makeMutationFn(), () => true], | ||
[makeMutationFn(), () => getByTestId('the-element-we-are-looking-for')], | ||
[ | ||
() => | ||
container.removeChild(getByTestId('the-element-we-are-looking-for')), | ||
() => getByTestId('the-element-we-are-looking-for'), | ||
], | ||
] | ||
|
||
const callback = jest | ||
.fn(() => { | ||
throw new Error('No more calls are expected.') | ||
}) | ||
.mockName('callback') | ||
.mockImplementation(() => true) | ||
|
||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
const promise = waitForElementToBeRemoved(callback, {container}).then( | ||
successHandler, | ||
errorHandler, | ||
) | ||
|
||
// One synchronous `callback` call is expected. | ||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
skipSomeTimeForMutationObserver() | ||
|
||
// No more expected calls without DOM mutations. | ||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
// Perform mutations one by one, waiting for each to trigger `MutationObserver`. | ||
for (const [mutationImpl, callbackImpl] of mutationsAndCallbacks) { | ||
callback.mockImplementation(callbackImpl) | ||
mutationImpl() | ||
skipSomeTimeForMutationObserver() | ||
} | ||
|
||
await promise | ||
|
||
expect(callback).toHaveBeenCalledTimes(1 + mutationsAndCallbacks.length) | ||
expect(successHandler).toHaveBeenCalledTimes(1) | ||
expect(successHandler.mock.calls[0]).toMatchSnapshot() | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
}) | ||
|
||
test('it waits characterData mutation', async () => { | ||
const {container} = render(`<div>initial text</div>`) | ||
|
||
const callback = jest | ||
.fn(() => container.textContent === 'initial text') | ||
.mockName('callback') | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
const promise = waitForElementToBeRemoved(callback, {container}).then( | ||
successHandler, | ||
errorHandler, | ||
) | ||
|
||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
expect(callback).toHaveBeenCalledTimes(1) | ||
|
||
skipSomeTimeForMutationObserver() | ||
|
||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
container.querySelector('div').innerHTML = 'new text' | ||
skipSomeTimeForMutationObserver() | ||
await promise | ||
|
||
expect(successHandler).toHaveBeenCalledTimes(1) | ||
expect(successHandler.mock.calls[0]).toMatchSnapshot() | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
expect(callback).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
test('it waits for the attributes mutation', async () => { | ||
const {container} = render(``) | ||
container.setAttribute('data-test-attribute', 'PASSED') | ||
|
||
const callback = jest | ||
.fn(() => container.getAttribute('data-test-attribute')) | ||
.mockName('callback') | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
const promise = waitForElementToBeRemoved(callback, { | ||
container, | ||
}).then(successHandler, errorHandler) | ||
|
||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
skipSomeTimeForMutationObserver() | ||
|
||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
container.removeAttribute('data-test-attribute') | ||
skipSomeTimeForMutationObserver() | ||
await promise | ||
|
||
expect(callback).toHaveBeenCalledTimes(2) | ||
expect(successHandler).toHaveBeenCalledTimes(1) | ||
expect(successHandler.mock.calls[0]).toMatchSnapshot() | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
}) | ||
|
||
test('it throws if timeout is exceeded', async () => { | ||
const {container} = render(``) | ||
|
||
const callback = jest.fn(() => true).mockName('callback') | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
const promise = waitForElementToBeRemoved(callback, { | ||
container, | ||
timeout: 70, | ||
mutationObserverOptions: {attributes: true}, | ||
}).then(successHandler, errorHandler) | ||
|
||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
container.setAttribute('data-test-attribute', 'something changed once') | ||
skipSomeTimeForMutationObserver(50) | ||
|
||
expect(callback).toHaveBeenCalledTimes(2) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
container.setAttribute('data-test-attribute', 'something changed twice') | ||
skipSomeTimeForMutationObserver(50) | ||
await promise | ||
|
||
expect(callback).toHaveBeenCalledTimes(3) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(1) | ||
expect(errorHandler.mock.calls[0]).toMatchSnapshot() | ||
}) | ||
|
||
test('it returns error immediately if there callback returns falsy value or error before any mutations', async () => { | ||
const {container, getByTestId} = render(``) | ||
|
||
const callbackForError = jest | ||
.fn(() => getByTestId('initial-element')) | ||
.mockName('callback') | ||
const callbackForFalsy = jest.fn(() => false).mockName('callback') | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
waitForElementToBeRemoved(callbackForError, { | ||
container, | ||
timeout: 70, | ||
mutationObserverOptions: {attributes: true}, | ||
}).then(successHandler, errorHandler) | ||
waitForElementToBeRemoved(callbackForFalsy, { | ||
container, | ||
timeout: 70, | ||
mutationObserverOptions: {attributes: true}, | ||
}).then(successHandler, errorHandler) | ||
|
||
expect(callbackForError).toHaveBeenCalledTimes(1) | ||
expect(callbackForFalsy).toHaveBeenCalledTimes(1) | ||
|
||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
await wait() | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(2) | ||
|
||
container.setAttribute('data-test-attribute', 'something changed once') | ||
skipSomeTimeForMutationObserver(50) | ||
|
||
expect(callbackForError).toHaveBeenCalledTimes(1) | ||
expect(callbackForFalsy).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(2) | ||
expect(errorHandler.mock.calls[0]).toMatchSnapshot() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'm fine with this being a snapshot, but please make it inline. |
||
expect(errorHandler.mock.calls[1]).toMatchSnapshot() | ||
}) | ||
|
||
test('works if a container is not defined', async () => { | ||
render(``) | ||
const el = document.createElement('p') | ||
document.body.appendChild(el) | ||
el.innerHTML = 'I changed!' | ||
const callback = jest | ||
.fn(() => el.textContent === 'I changed!') | ||
.mockName('callback') | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
let promise | ||
if (typeof window === 'undefined') { | ||
promise = waitForElementToBeRemoved(callback, {container: document}).then( | ||
successHandler, | ||
errorHandler, | ||
) | ||
} else { | ||
promise = waitForElementToBeRemoved(callback).then( | ||
successHandler, | ||
errorHandler, | ||
) | ||
} | ||
|
||
skipSomeTimeForMutationObserver() | ||
|
||
expect(callback).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
el.innerHTML = 'Changed!' | ||
skipSomeTimeForMutationObserver() | ||
await promise | ||
|
||
expect(callback).toHaveBeenCalledTimes(2) | ||
expect(successHandler).toHaveBeenCalledTimes(1) | ||
expect(successHandler.mock.calls[0]).toMatchSnapshot() | ||
expect(errorHandler).toHaveBeenCalledTimes(0) | ||
|
||
document.getElementsByTagName('html')[0].innerHTML = '' // cleans the document | ||
}) | ||
|
||
test('throws an error if callback is not a function', async () => { | ||
const successHandler = jest.fn().mockName('successHandler') | ||
const errorHandler = jest.fn().mockName('errorHandler') | ||
|
||
let promise | ||
if (typeof window === 'undefined') { | ||
promise = waitForElementToBeRemoved(undefined, {container: document}).then( | ||
successHandler, | ||
errorHandler, | ||
) | ||
} else { | ||
promise = waitForElementToBeRemoved().then(successHandler, errorHandler) | ||
} | ||
|
||
skipSomeTimeForMutationObserver() | ||
await promise | ||
|
||
expect(errorHandler).toHaveBeenCalledTimes(1) | ||
expect(errorHandler.mock.calls[0]).toMatchSnapshot() | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these snapshots are at all necessary and just make it harder to know the intent of the tests. Could we change these to be regular assertions instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kentcdodds I have almost removed the snapshot to inline snapshots(Which should be working as regular assertions). But, because we are also passing the error we got from the
getByTestId
etc, in that case, a snapshot will be a good thing I guess.