-
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
Wait for element to be removed #218
Conversation
cad86d5
to
a349725
Compare
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.
This is looking really good to me.
Could we add one test (maybe the first) that's a typical use case example?
|
||
// Using `setTimeout` >30ms instead of `wait` here because `mutationobserver-shim` uses `setTimeout` ~30ms. | ||
const skipSomeTimeForMutationObserver = (delayMs = 50) => | ||
skipSomeTime(delayMs, 50) |
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.
Do you think we could use jest fake timers for this? I've found that tests that rely on setTimeout are pretty unreliable in CI environments especially.
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.
Sounds good, I will give a shot.
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 tried to use fakeTimer
, but it does not seem to be an ideal place to use timeFakers.
As per I know, setTimeout
are unreliable because of time-outs in CI right? How important do you think it is for this PR. If it's pretty important, I will try to look at it in more detail.
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 just don't want tests failing randomly on CI. And that happens when relying on setTimeout quite a bit.
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 have changed the tests. Can you please take a look.
There are other places such as wait-for-element.js
, where setTimeout
is heavily used(I know this because my test cases are mostly inspired by that). If that's something you want to change I can create another PR to get rid of setTimeout
from tests.
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.
Should it succeed or fail if the element did not exist in the first place? It may be confusing, "ToBeRemoved" imho assumes just the change from "is present" to "is not present".
I think that's a good idea as well 👍 |
I have changed it to throw |
@@ -73,9 +74,11 @@ test('it waits for the callback to throw error or a falsy value and only reacts | |||
for (const [mutationImpl, callbackImpl] of mutationsAndCallbacks) { | |||
callback.mockImplementation(callbackImpl) | |||
mutationImpl() | |||
await skipSomeTimeForMutationObserver() // eslint-disable-line no-await-in-loop | |||
skipSomeTimeForMutationObserver() // eslint-disable-line no-await-in-loop |
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.
Doesn't this still need the await?
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 so. Now the test runs synchronously because we're using jest fake timers.
return new Promise((resolve, reject) => { | ||
if (typeof callback !== 'function') { | ||
reject( | ||
'waitForElementToBeRemoved requires a callback as the first parameter', |
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.
Nit: requires a function. Callback is jargon.
|
||
// Check if the element is not present synchronously, | ||
// As the name waitForElementToBeRemoved should check `present` --> `removed` | ||
;(function checkElementPresent() { |
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.
Is there a reason this code needs to be in an IIFE?
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.
Not much, I was just hoping to separate functionality.
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.
Will it have a bad impact?
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 guess not. Just not a coding style we have anywhere else in the codebase. Feels unnecessary to me.
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.
Ok, will get rid of it.
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.
Getting close! Thanks for working on this!
return new Promise((resolve, reject) => { | ||
if (typeof callback !== 'function') { | ||
reject( | ||
'waitForElementToBeRemoved requires a function as the first parameter', |
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 think this should be an error object rather than just a string so we get a stacktrace.
// As the name waitForElementToBeRemoved should check `present` --> `removed` | ||
try { | ||
const result = callback() | ||
if (!result) { |
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.
Should we be more specific in checking that the result is a single element (or non-empty array of elements)? If it's not then the error can be more specific as well: "The callback function which was passed did not return an element or non-empty array of elements. waitForElementToBeRemoved requires that the element(s) exist before waiting for removal."
onDone(new Error('Element is not present in the DOM.'), true) | ||
} | ||
} catch (error) { | ||
onDone(new Error('Element is not present in the DOM.'), true) |
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 think this should be onDone(error)
so we don't swallow the error. Also, what's the purpose of true
as the second argument here?
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 Won't it be confusing for users? by doing onDone(error)
? Because then they would not know the error is telling them that waitForElementToBeRemoved
requires that the element(s) exist before waiting for removal.
But, yeah, the error might have occurred due to other reasons as well and in that case, we should be sending back the same errors.
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.
The perfect solution would have been to instanceof
check for the GetElementError
. But we do not have our own error class right?
I will just send back the error for now. But, making our own error class and checking for that, if the error is GetElementError
send the above message else sending back the caught error itself seems a good solution to me.
try { | ||
const result = callback() | ||
if (!result) { | ||
onDone(new Error('Element is not present in the DOM.'), true) |
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.
What's the purpose of the second argument of true
here?
@@ -0,0 +1,51 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
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.
src/__tests__/__snapshots__/wait-for-element-to-be-removed.js.snap
Outdated
Show resolved
Hide resolved
…m/Tolsee/dom-testing-library into ts-wait-for-element-to-be-removed
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.
Thanks! We're getting closer!
await promise | ||
|
||
expect(successHandler).toHaveBeenCalledTimes(1) | ||
expect(successHandler.mock.calls[0]).toMatchInlineSnapshot(` |
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.
Any reason this couldn't be simplified to:
expect(successHandler).toHaveBeenCalledWith(true)
Or you could also replace this line and the one above with:
expect(successHandler.mock.calls).toEqual([true])
I don't really care either way, but using snapshots for this doesn't make any sense to me personally.
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.
Yeah, make sense. I am a little 🆕 to jest
that is the only reason.
expect(callbackForFalsy).toHaveBeenCalledTimes(1) | ||
expect(successHandler).toHaveBeenCalledTimes(0) | ||
expect(errorHandler).toHaveBeenCalledTimes(3) | ||
expect(errorHandler.mock.calls[0]).toMatchSnapshot() |
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 guess I'm fine with this being a snapshot, but please make it inline.
One of my first contribution to this library as well as open source. Really enjoying the journey. |
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.
After looking closer at the tests I can't help but feel like they could be simplified. I hope you don't mind me working on that a little on my own.
@@ -0,0 +1,334 @@ | |||
import {waitForElementToBeRemoved} from '../' | |||
// adds special assertions like toBeTruthy |
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.
toBeTruthy is built-into jest. This adds things like toBeInTheDocument. But I don't think we need this comment anyway. Please remove it.
Of course not. |
@kentcdodds Did you get a chance to review the tests? If you do not have enough time, you can give me a direction and I will work on the test. I want this to be merged 😄 |
Sorry, been busy. I'll try to get this done this weekend. |
Alrighty. I'm happy with these tests now. Once the build passes then I'll merge/release it :D |
c9be668
to
17f136f
Compare
{ | ||
...baseConfig, | ||
displayName: 'node', | ||
testEnvironment: 'jest-environment-node', |
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.
Honestly, running all the exact same tests in node
led to some poor testing practices and I didn't like the over-testing either. If someone really wants to have tests like this, then we can add a other/node/__tests__/
directory with a other/node/jest.config.js
file. I want the tests to be different.
Also, I don't think it's worthwhile to have tests for node. I'm pretty sure there's a very small number of people who benefit from this.
@@ -6,7 +6,6 @@ cache: | |||
notifications: | |||
email: false | |||
node_js: | |||
- 'node' |
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.
Doing this opens us up to stuff like this: https://stackoverflow.com/questions/55059748/travis-jest-typeerror-cannot-assign-to-read-only-property-symbolsymbol-tostr
I think the benefit of testing an unstable version of node isn't worth stuff like this.
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.
This is ready to go!
🎉 This PR is included in version 3.17.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
What: Adding
waitForElementToBeRemoved
feature.Why: As discussed in #206, it would be easier to use
waitForElementToBeRemoved
to check whether the element is removed or not.How: It is similar to
waitForElement
. Can be used aswaitForElementToBeRemoved(() => getByTestId('data-testid'))
, actually just likewaitForElement
with any other query functions.Checklist: