testing-library / react-hooks-testing-library

🐏 Simple and complete React hooks testing utilities that encourage good testing practices.
https://react-hooks-testing-library.com
MIT License
5.25k stars 231 forks source link

`waitFor` doesn't work if jest fake timers are used #631

Open DennisSkoko opened 3 years ago

DennisSkoko commented 3 years ago

Problem

When using waitFor when Jest has been configured to use fake timers then the waitFor will not work and only "polls" once. After that the test just hangs until Jest comes in and fails the test with that the test exceeds the timeout time. Below is some code that showcases the problem.

import { renderHook } from '@testing-library/react-hooks'

it('does not work', async () => {
  jest.useFakeTimers()

  const { waitFor } = renderHook(() => {})

  await waitFor(() => {
    console.log('poll') // Is printed just once
    expect(false).toBe(true)
  }, { timeout: 25, interval: 10 })

  // Fails with Exceeded timeout of 5000 ms for a test.
})

Basically the waitFor from @testing-library/react-hooks is using the faked setTimeout or setInterval which prevents it from working correctly.

There is a workaround (see suggested solution) but I recommend providing a nice error message when waitFor is used together with faked timers or maybe change the implemenation so it will work with fake timers.

Suggested solution

I found this issue and it seems that person has already been fixed in @testing-library/dom. From my perspective I can suggest maybe reuse that function instead of implementing it yourselves but I don't really know the internal structure / code.

But after finding that issue and realizing that is has been fixed there, then I use the following code as a workaround which works fine.

import { waitFor } from '@testing-library/react'

it('works', async () => {
  jest.useFakeTimers()

  await waitFor(() => {
    console.log('poll') // Is printed twice
    expect(false).toBe(true)
  }, { timeout: 25, interval: 10 })

  // Fails with false is not equal to true
})

A more real world scenario

If curios on the actual problem I'm facing is to test the following hook:

function useSomething({ onSuccess }) {
  const poll = useCallback(async () => {
    const result = await fetch(/* ... */)
    if (result.ok) onSuccess()
  }, [onSuccess])

  useEffect(() => {
    const id = setInterval(() => { poll() }, 2000)
    return () => clearInterval(id)
  }, [poll])
}

What I want to do is test that it invokes the onSuccess function on a successfull poll.

it('invokes the `onSuccess` on successfull poll', async () => {
  const onSuccess = jest.fn()
  jest.useFakeTimers()

  const { waitFor } = renderHook(() => useSomething({ onSuccess }))

  jest.runOnlyPendingTimers()
  await waitFor(() => expect(onSuccess).toHaveBeenCalled())
})
mpeyper commented 3 years ago

When I was reading this I was thinking the only way I could think of solving it would be to query jest about the timers, so it's interesting that that's effectively how they solved it in dom-testing-library.

Unfortunately we can use the same function as we do not depend on it, or the DOM in general, which their solution uses. That said, the way we waitFor is similar so we can likely take some clues from them.

If anyone is interested in working on the fix for this, I'm more than happy the help you out.

chris110408 commented 3 years ago

@mpeyper I would like to contribute, I think the outline for this solution will be a similar solution used by dom-testing-library

  1. We detect if we're in an environment that's faking out timers. for this step, I have a question, should we use their helper or we need helps by ourselves for the jestFakeTimersAreEnabled function and the setImmediateFn

  function jestFakeTimersAreEnabled() {
    return hasJestTimers()
        ? usedFakeTimers
        :false
  }

 function hasJestTimers() {
    return (
        typeof jest !== 'undefined' &&
        jest !== null &&
        typeof jest.useRealTimers === 'function'
    )
  }

  const usedFakeTimers = Object.entries(timerAPI).some(
      ([name, func]) => func !== globalObj[name],
  )

const globalObj = typeof window === 'undefined' ? global : window

let   setImmediateFn = globalObj.setImmediate || setImmediatePolyfill,

function setImmediatePolyfill(fn) {
  return globalObj.setTimeout(fn, 0)
}
  1. Revise wait function


 const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => {
    ....
   const waitForFakeTimer=async (interval)=>{
      let finished = false
      while (!finished) {
        if (!jestFakeTimersAreEnabled()) {
           throw new Error(
              `Changed from using fake timers to real timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to real timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830`,
          )
        }
        jest.advanceTimersByTime(interval)
        await new Promise(r => setImmediate(r))
      }
    }
  ....
    if (!checkResult()) {

   ....
      await act(() => Promise.race([waitForResult(), timeoutPromise(),usingJestFakeTimers && waitForFakeTimer(interval)].filter(Boolean)))
      } else {
        await act(() => Promise.race([waitForResult(),usingJestFakeTimers && waitForFakeTimer(interval)].filter(Boolean)))
      }

}
mpeyper commented 3 years ago

Sorry for the delay in responding @chris110408. Any contributions towards this are very welcome.

I think taking a similar approach to dom-testing-library is a good idea and I proposed solution seems fine to me (assuming it works).

My only other thought is the inclusion of timeoutPromise() or not in the race feels like it could be achievable with the .filter(Boolean) as well, but I'll hold judgement on the readability until the PR 😉.

chris110408 commented 3 years ago

Thanks @mpeyper I will work on the PR

chris110408 commented 3 years ago

I have been trying to find the fix for a typescript error. image I am not sure how to fix it yet. and I am not able to use // @ts-ignore to quickly fix it.
I guess I also should not use // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error 😄 @mpeyper I need some help for this problem

mpeyper commented 3 years ago

Give this a shot:

  Object.entries(timerAPI).some(
    ([name, func]) =>  func !== globalObj[name as keyof typeof timerAPI]
  )

Object.entries returns [string, T][] so you need to destructure the tuple (the ([name, func]) => ... change) and the first index of the tuple is annoyingly a string instead the the keys of the object (there are reasons for this though, so it needs to be cast in order to use it as an index of globalObj.

Hope this helps.

chris110408 commented 3 years ago

Give this a shot:

  Object.entries(timerAPI).some(
    ([name, func]) =>  func !== globalObj[name as keyof typeof timerAPI]
  )

Object.entries returns [string, T][] so you need to destructure the tuple (the ([name, func]) => ... change) and the first index of the tuple is annoyingly a string instead the the keys of the object (there are reasons for this though, so it needs to be cast in order to use it as an index of globalObj.

Hope this helps.

It works Thanks a lot for the explanation

chris110408 commented 3 years ago

@mpeyper I has been working on this issue recently, I changed the fackerTimer detection code to this

const usedFakeTimers = ()=>!!(global.setTimeout.mock||global.setTimeout.clock)

However, I have a hard time to understand the issue,

import { renderHook } from '@testing-library/react-hooks'

it('does not work', async () => {
  jest.useFakeTimers()

  const { waitFor } = renderHook(() => {})

  await waitFor(() => {
    console.log('poll') // Is printed just once
    expect(false).toBe(true)
  }, { timeout: 25, interval: 10 })

  // Fails with Exceeded timeout of 5000 ms for a test.
})

this test will never pass no matter we use useFakeTimers or not.

and I change the test code to

it('useFakeTimers test', async () => {
        jest.useFakeTimers()
        const { waitFor } = renderHook(() =>{})

        let actual = 0
        const expected = 1

        setTimeout(() => {
                actual = expected
        }, 200)
// show Fails with Exceeded timeout of 5000 ms for a test. if we do not use  jest.advanceTimersByTime(200)
        jest.advanceTimersByTime(200)
        let complete = false
        await waitFor(() => {
                expect(actual).toBe(expected)
                complete = true
        },{ interval: 50 })
        expect(complete).toBe(true)
})

our test library does not have any problem running it.

Could you please help me confirm what is our problem here?

Should I just add this unit test to prove waitFor works when using jest.useFakeTimers() ?

mpeyper commented 3 years ago

To be honest, I don't use fake timers when I write tests, so I'm also unsure of what the desired outcome is.

I think the difference you have here is advanceTimersByTime is going to advance the internal timers as well, even as new timers are created for the different intervals, but runOnlyPendingTimers will only advance existing timers and the new ones never advance.

@DennisSkoko is there more info you can provide to help @chris110408 out?

chris110408 commented 3 years ago

To be honest, I don't use fake timers when I write tests, so I'm also unsure of what the desired outcome is.

I think the difference you have here is advanceTimersByTime is going to advance the internal timers as well, even as new timers are created for the different intervals, but runOnlyPendingTimers will only advance existing timers and the new ones never advance.

@DennisSkoko is there more info you can provide to help @chris110408 out?

@DennisSkoko @mpeyper

I add another test that uses the runOnlyPendingTimers and useFakeTimer and both test successfully pass

mpeyper commented 3 years ago

Now I'm even more confused about what the actual issue is here. There must be a reason dom-testing-library has such an implementation?

Do they want it to work automagically without explicitly advancing the timers? (Some of the samples provided imply this)

chris110408 commented 3 years ago

Now I'm even more confused about what the actual issue is here. There must be a reason dom-testing-library has such an implementation?

Do they want it to work automagically without explicitly advancing the timers? (Some of the samples provided imply this)

I am also confused about the actual issue. Yes, the dom-testing-library solution is useFakeTimer and without explicitly advancing the timers? in the test. However, I think it is comment practice to use advanceTimersByTime when use useFakeTimeber in the test. (I have not to use useFakeTimer before. my assumption was made by reading the Jest document the timer mock is used for setting up a testing environment not depend on real-time to elapse and jest mock timer functions that allow you to control the passage of time ) and I am not sure why they make it work automagically without explicitly advancing the timers? Please let me know if you want me to make the wait for function of react-hooks-testing-library works in that way.

DennisSkoko commented 3 years ago

Hi @chris110408 @mpeyper, sorry for the late response.

From my point of view, what is happening is that the implementation of waitFor is using the setTimeout/setInterval which are mocked when useFakeTimers is used, resulting in the polling logic for waitFor will just hang.

and I change the test code to

it('useFakeTimers test', async () => {
        jest.useFakeTimers()
        const { waitFor } = renderHook(() =>{})

        let actual = 0
        const expected = 1

        setTimeout(() => {
                actual = expected
        }, 200)
// show Fails with Exceeded timeout of 5000 ms for a test. if we do not use  jest.advanceTimersByTime(200)
        jest.advanceTimersByTime(200)
        let complete = false
        await waitFor(() => {
                expect(actual).toBe(expected)
                complete = true
        },{ interval: 50 })
        expect(complete).toBe(true)
})

our test library does not have any problem running it.

I think the reason this works fine is that the waitFor will never fail in this case. You are advancing time before invoking the waitFor which will result in the initial poll being successful. Here is hopefully a better example that showcases the problem:

it('asd', async () => {
  jest.useFakeTimers()

  const fn = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true)

  const { waitFor } = renderHook(() => {})

  await waitFor(() => {
    expect(fn()).toBe(true)
  })
})

Here we can see that test cases fails with a Jest timeout. Reason being that waitFor does an initial poll that checks in the function does not throw, but in this case it will since false does not equal true. As soon as the initial poll from waitFor fails, then waitFor will use setTimeout/setInterval to poll. Now our problem is that waitFor will never poll since the setTimeout/setInterval has been mocked.

Hopefully this provides the information you are looking for.

mpeyper commented 3 years ago

Yep, that makes sense to me.

Just to clarify, no amount of running pending timers or advancing them before the waitFor will make it pass because the timeout hasn't been created yet, right?

What if the run/advance was in the waitFor callback?

chris110408 commented 3 years ago

Yep, that makes sense to me.

Just to clarify, no amount of running pending timers or advancing them before the waitFor will make it pass because the timeout hasn't been created yet, right?

What if the run/advance was in the waitFor callback?

For that test case the the run/advance was in the waitFor callback is also do work. I am working on a solution to fix it

bsk26 commented 3 years ago

We're seeing what I believe is a related issue where waitFor seems to pass regardless of the condition. For example, the following test passes,

            const { result, waitFor } = renderHook(
                             ...
            );

            waitFor(() => {
                expect(false).toBe(true);
            });

This also appears to be related to fake timers -- but in this case with the legacy option (which is discouraged by the main testing library but seems to work OK). Apologies for not including a full repro -- but figured it was worth commenting in case others are seeing the same issue (this is non urgent but is simply blocking our adoption of @testing-library/react-hooks with the rest of `testing-library)

chris110408 commented 3 years ago

ed to fake timers -- but in this case with the legacy option (which is discouraged by the main testing library but seems to work OK). Apologies for not including a full repro -- but figured it was worth commenting in case others are seeing the same issue (this is non urgent but is simply blocking our

Sorry I was distract by my work and personal stuff. I will work on this issue this weekend.

chris110408 commented 3 years ago

it looks Promise.race() will behave unexpectedly when use with fake timer https://github.com/facebook/jest/issues/10258. That is some thing we need to consider for fixing this bug

mpeyper commented 3 years ago

Interesting. There was also another issue raised in discord around our use of Promise.race and leaving open handles that jest detects and warns about in some circumstances. I've actually got a branch I'll be pushing up later today that removes the Promise.race calls all together.

chris110408 commented 3 years ago

We're seeing what I believe is a related issue where waitFor seems to pass regardless of the condition. For example, the following test passes,

          const { result, waitFor } = renderHook(
                             ...
          );

          waitFor(() => {
              expect(false).toBe(true);
          });

This also appears to be related to fake timers -- but in this case with the legacy option (which is discouraged by the main testing library but seems to work OK). Apologies for not including a full repro -- but figured it was worth commenting in case others are seeing the same issue (this is non urgent but is simply blocking our adoption of @testing-library/react-hooks with the rest of `testing-library)

Yes Thanks for the note because of https://github.com/testing-library/react-hooks-testing-library/blob/main/src/core/asyncUtils.ts#L64 we never throw the error and return a false instead. Since the result ===false it will pass the if check
https://github.com/testing-library/react-hooks-testing-library/blob/main/src/core/asyncUtils.ts#L69 and trigger the new TimeoutError(waitFor, timeout)

we need to know why we use const safeCallback to fix this issue

mpeyper commented 3 years ago

safeCallback is there to enable the use of things like:

await waitFor(() => {
  expect(result.current.example).toBe(somethingExpected)
})

The intent is to wait until the callback stops throwing an error.

We also support waiting for a truth value to be returned:

await waitFor(() => result.current.isReady)

safeCallback turns the error version in the boolean version so the implementation only has to deal with one.

chris110408 commented 3 years ago

safeCallback is there to enable the use of things like

await waitFor(() => {
  expect(result.current.example).toBe(somethingExpected)
})

The intent is to wait until the callback stops throwing an error. We also support waiting for a truth value to be returned:

await waitFor(() => result.current.isReady)

safeCallback turns the error version in the boolean version so the implementation only has to deal with one.

in this case, no mater what error we have we will always get a timeout error. is that an expected behavior?https://github.com/testing-library/react-hooks-testing-library/blob/main/src/core/asyncUtils.ts#L70

mpeyper commented 3 years ago

in this case, no mater what error we have we will always get a timeout error. is that an expected behavior?

Yes, it either passes or times out trying.

chris110408 commented 3 years ago

in this case, no mater what error we have we will always get a timeout error. is that an expected behavior?

Yes, it either passes or times out trying.

Since the example @DennisSkoko provided is an expected behavior, should we continue work on this issue?

bsk26 commented 3 years ago

Does this mean the expected behavior for waitFor is effectively different than for @testing-library/react? (obviously this would not be ideal)

mpeyper commented 3 years ago

@chris110408 Yes, I think there is still work to be done here. Using fake timers stills fails in the scenario described here.

@bsk26 I would not expect that test to pass, regardless of the use of fake timers or not. The callback gets called synchronously once before using timeouts to poll for subsequent checks. With the current implementation, I'd expect the test to timeout with our error if not using fake timers, or jest's timeout if you are. Can you please that you are awaiting the result of waitFor otherwise the test will breeze past it and appear to pass because it isn't actually waiting for the promise to resolve:

const { result, waitFor } = renderHook(
  ...
);

await waitFor(() => {
  expect(false).toBe(true);
});

Does this mean the expected behavior for waitFor is effectively different than for @testing-library/react? (obviously this would not be ideal)

I assume you mean other than us not supporting the use of fake timers (yet)?

There has been a bit of deviation in our implementations which is not ideal, but for the most part the core use case is consistent, that is, it will run the callback on an interval until it stops throwing an error. We implemented it to also work of returning a boolean (unsupported by @testing-library/dom, where @testing-library/react get's it's implementation from) to simplify a common use case we see:

const { result, waitFor } = renderHook(() => useSomething())

await waitFor(() => !result.current.loading)

expect(result.current.data).toBe(...)

I suspect that this use case is less useful to @testing-library/react as the result of a loading state for them is a spinner in the DOM so they would use waitForElementToBeRemoved to check for it, but we deal with much more raw data being returned from hooks so we cannot rely on something external changing.

The other main difference is they now support returning a Promise from their callback which will delay running another interval check until the promise resolves. I actually didn't realise this was a thing until looking this now to answer you and I conceptually like it and think it would be a great feature for us to support as well.

There are also some subtle differences in that they they run additional checks with a MutationObserver whereas we run them if render is called. The difference is simply because hooks do not rely on DOM changes, but rather component changes and in many cases we don't even have a DOM in the test environment so we could not rely on the same implementation.

Fundamentally though, the implementation is different because our use cases and our dependencies are different. We try to offer the best utilities specifically for testing hook functions, rather than ensuring 100% API consistency with the libraries designed for testing DOM output.

chris110408 commented 3 years ago

@mpeyper the createTimeoutController is awesome and I am able to fix this issue. just need to modify the code to

if (timeout) {
          timeoutId = setTimeout(() => {
            timeoutController.timedOut = true
            timeoutCallbacks.forEach((callback) => callback())
            resolve()
          }, timeout)

         if (jestFakeTimersAreEnabled()) {
              jest.advanceTimersByTime(timeout-1)
          }
        }

however, there is one typescript lint error I am not sure how to solve and need your help.

This function was cloned from @testing-library/dom to detect whether or not the user usesjest.useFakeTimers

export const jestFakeTimersAreEnabled = () => {
  /* istanbul ignore else */
  if (typeof jest !== 'undefined' && jest !== null) {
    return (
      // legacy timers
      setTimeout._isMockFunction === true ||
      // modern timers
      Object.prototype.hasOwnProperty.call(setTimeout, 'clock')
    )
  }
  // istanbul ignore next
  return false
}

However, it looks the global setTimeout does not have the props of _isMockFunction, and pop out the following lint error image

Could you please help me provide some suggestions for how to handle this lint error?

I should be able to close this ticket after I could handle this lint error. :)

mpeyper commented 3 years ago

Hi @chris110408,

You can use jest.isMockFunction(setTimeout) instead for that one.

When making these changes, please add this test to the fake timer tests:

    test('should waitFor arbitrary expectation to pass when fake timers are not advanced explicitly', async () => {
      const fn = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true)

      const { waitFor } = renderHook(() => null)

      await waitFor(() => {
        expect(fn()).toBe(true)
      })
    })

(and a bunch of other cases probably)

chris110408 commented 3 years ago

Hi @chris110408,

You can use jest.isMockFunction(setTimeout) instead for that one.

When making these changes, please add this test to the fake timer tests:

    test('should waitFor arbitrary expectation to pass when fake timers are not advanced explicitly', async () => {
      const fn = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true)

      const { waitFor } = renderHook(() => null)

      await waitFor(() => {
        expect(fn()).toBe(true)
      })
    })

(and a bunch of other cases probably)

688 PR has been created to close this ticket

mpeyper commented 3 years ago

Thanks @chris110408. We'll move implementation discussion to the PR now. I'll try to find time to take a look later today.

github-actions[bot] commented 3 years ago

:tada: This issue has been resolved in version 8.0.0-alpha.1 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

joeythomaschaske commented 2 years ago

waitFor under the hood uses setInterval which is also faked when using jest.useFakeTimers.

I'm very surprised no one has mentioned to tell jest not to fake other things it fakes.

This is an exhaustive list of things jest fakes when using jest.useFakeTimers. IMO it's best to be very selective on what you let it fake, some things are not obvious

Can someone try jest.useFakeTimers({doNotFake: ['setInterval']});?

5c077yP commented 2 years ago

Hey all, seems there was a PR merged, but into the alpha branch and not main ... is this at some point planned to be released ?

mpeyper commented 2 years ago

The implementation in the alpha branch has some issues and I never found time to get to the bottom of it. It’s unlikely we’ll ever merge that into the main branch now.

For what it’s worth, the new RTL version already supports fake timers. I’m not sure about the new RNTL version though.

enzoferey commented 2 years ago

Very good pointer @joeythomaschaske 🎯

Indeed, waitFor uses setInterval, so if you use jest.useFakeTimers({ doNotFake: ["setInterval"] }) as you suggested it works.

For Vitest users, the API is a bit better as you can directly specify the only thing you want to fake. For example: vi.useFakeTimers({ toFake: ["setTimeout"] }).

sajTempler commented 2 years ago

I wanted only Date to be faked actually (while allowing all promises and intervals) hence I had to list implicitly other things as well:

jest
.useFakeTimers({
  doNotFake: [
    'setImmediate',
    'setInterval',
    'setTimeout',
    'cancelAnimationFrame',
    'cancelIdleCallback',
    'clearImmediate',
    'clearInterval',
    'clearTimeout',
    'nextTick',
    'queueMicrotask',
  ],
})
.setSystemTime(new Date('2022-07-31'));
tim-phillips commented 1 year ago

Just had to update RTL, which resolved it for me.

seanzer commented 1 year ago

I've been using this solution, but depending on what you want it might not be all that useful

async function waitFor(cb) {
  let success = false;
  do {
    try {
      success = cb();
    } catch {}
    jest.runOnlyPendingTimers();
    await Promise.resolve();
  } while (!success);
}
adbutterfield commented 1 year ago

Thanks @enzoferey!!! I was struggling with this problem migrating tests to vite for a few hours... vi.useFakeTimers({ toFake: ['Date'], now: new Date(2022, 2, 20, 8, 0, 0) }); Works like a charm!

flakey-bit commented 1 year ago

As a workaround, you can switch back to using real timers immediately before invoking waitFor()

e.g.

// Arrange
jest.useFakeTimers();

// Act
doStuffThatTakesALongTime();

await jest.advanceTimersByTimeAsync(MAX_EXPECTED_TIME); // Fast forward

// Assert
jest.useRealTimers(); // Switch back to real timers so that we can use waitFor()
await waitFor(() => expect(inputControl).toBeEnabled()); 
gil-viz commented 11 months ago

@flakey-bit approach worked for me even without the jest.advanceTimersByTimeAsync, amazing, thanks!

jcready commented 10 months ago

The implementation in the alpha branch has some issues and I never found time to get to the bottom of it. It’s unlikely we’ll ever merge that into the main branch now.

This is a bit disappointing. Especially since the changelog between 8.0.0-alpha.1 and 8.0.0 did not mention that this was effectively removed from the v8 major version. We just spent time upgrading from v7 to v8 for the sole purpose of getting this fix just to find out that it's not there.

For what it’s worth, the new RTL version already supports fake timers.

I don't see how this is relevant. The new RTL versions all require upgrading to react v18 which is no small ask.

mpeyper commented 10 months ago

My apologies @jcready for the waste of your time.

This library has been effectively deprecated for quite some time now.

While I understand upgrading to React 18 is no small feat, I do feel like it was a relevant thing for me to point out as many of my users would be looking to make that upgrade and along with it would likely need to migrate away from this library anyway. After all, React 18 has been out now for almost 2 years now.

All that being said, I’ll update the release notes to mention that it was not included to hopefully save someone else from wasting their time also.

jcready commented 10 months ago

This library has been effectively deprecated for quite some time now.

I'm not sure if you've actually read through that issue thread, but on top of the major react 18 changes it sounds like we'd have to switch to an utterly crippled version of renderHook.

After all, React 18 has been out now for almost 2 years now.

React 18 is the Python 3 of version changes.

mpeyper commented 10 months ago

Oh, I definitely had opinions about the changes, but ultimately I was not prepared to continue supporting an offshoot of functionality from the main RTL version which would largely serve as a source of confusion to many users.

Admittedly, I don’t think we have done a good enough job in actually deprecating this library and thats largely been due to changes in my life distracting me (I don’t work with react or even JavaScript anymore) and the burnout I was feeling (largely caused by the thread I linked above) in actually supporting a dying library.

I suggest if the new version is missing features that you use, and there is a good use case for them, raise an issue in their repo to add support for it.