Skip to content
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

Merged

Conversation

Tolsee
Copy link
Contributor

@Tolsee Tolsee commented Feb 22, 2019

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 as waitForElementToBeRemoved(() => getByTestId('data-testid')), actually just like waitForElement with any other query functions.

Checklist:

  • Documentation added to the docs site
  • Typescript definitions updated
  • Tests
  • Ready to be merged
  • Added myself to contributors table

@Tolsee Tolsee changed the title Ts wait for element to be removed Wait for element to be removed Feb 22, 2019
@Tolsee Tolsee force-pushed the ts-wait-for-element-to-be-removed branch from cad86d5 to a349725 Compare February 22, 2019 19:01
Copy link
Member

@kentcdodds kentcdodds left a 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)
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

@Tolsee Tolsee Feb 24, 2019

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.

Copy link
Collaborator

@sompylasar sompylasar left a 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".

@kentcdodds
Copy link
Member

I think that's a good idea as well 👍

@Tolsee
Copy link
Contributor Author

Tolsee commented Feb 23, 2019

@sompylasar

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 have changed it to throw Error when there is no element at starting.

@@ -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
Copy link
Collaborator

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?

Copy link
Member

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',
Copy link
Collaborator

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() {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@kentcdodds kentcdodds left a 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',
Copy link
Member

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) {
Copy link
Member

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)
Copy link
Member

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?

Copy link
Contributor Author

@Tolsee Tolsee Feb 26, 2019

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.

Copy link
Contributor Author

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)
Copy link
Member

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
Copy link
Member

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?

Copy link
Contributor Author

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/wait-for-element-to-be-removed.js Show resolved Hide resolved
Copy link
Member

@kentcdodds kentcdodds left a 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(`
Copy link
Member

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.

Copy link
Contributor Author

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()
Copy link
Member

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.

@Tolsee
Copy link
Contributor Author

Tolsee commented Feb 28, 2019

Thanks! We're getting closer!

One of my first contribution to this library as well as open source. Really enjoying the journey.

Copy link
Member

@kentcdodds kentcdodds left a 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
Copy link
Member

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.

@Tolsee
Copy link
Contributor Author

Tolsee commented Feb 28, 2019

@kentcdodds

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.

Of course not.

@Tolsee
Copy link
Contributor Author

Tolsee commented Mar 9, 2019

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

@kentcdodds
Copy link
Member

Sorry, been busy. I'll try to get this done this weekend.

@kentcdodds
Copy link
Member

Alrighty. I'm happy with these tests now. Once the build passes then I'll merge/release it :D

@kentcdodds kentcdodds force-pushed the ts-wait-for-element-to-be-removed branch from c9be668 to 17f136f Compare March 9, 2019 20:55
{
...baseConfig,
displayName: 'node',
testEnvironment: 'jest-environment-node',
Copy link
Member

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'
Copy link
Member

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.

Copy link
Member

@kentcdodds kentcdodds left a 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!

@kentcdodds kentcdodds merged commit 1727a49 into testing-library:master Mar 9, 2019
@kentcdodds
Copy link
Member

🎉 This PR is included in version 3.17.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants