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

Cant waitFor dom changes introduced by on:introend #206

Closed
MoritzHorch opened this issue Sep 4, 2022 · 8 comments · Fixed by #311 or testing-library/testing-library-docs#1363
Closed

Comments

@MoritzHorch
Copy link

Hello everyone,

I currently have an app which plays some animation before being initialized, which in this case is a simple boolean. Depending on this variable, I render different content.

So if I have something like this:

<script>
  import { blur } from 'svelte/transition';
  import { bounceOut } from 'svelte/easing';

  export let initialized = false;
</script>

<div in:blur={{ duration: 1500, easing: bounceOut }} on:introend={() => { initialized = true}}>
  {#if initialized}
    <p>Hello</p>
  {:else}
    <p>One moment...</p>
  {/if}
</div>

and my test looks like this:

 it('doesnt work', async () => {
    render(Hello, { intro: true });

    expect(screen.getByText('One moment...')).toBeDefined();

    await fireEvent.animationEnd(screen.getByTestId('hello'));
    await waitFor(() => expect(screen.getByText('Hello')).toBeDefined(), {
      timeout: 2500,
    });
  });

The waitFor never succeeds. I even tried forcing an animation end with fireEvent.animationEnd. Here is a minimal reproduction of this issue: https://stackblitz.com/edit/vitest-dev-vitest-whwfuk?file=test/hello.test.ts

Hope somebody can help and provide a solution for this :) Thanks in advance!

@efstajas
Copy link

efstajas commented Sep 23, 2022

I have a similar issue:

https://github.com/radicle-dev/drips-app-v2/blob/70fda9401f3b46fb87d43668ecbe0d057d691cb2/src/lib/components/flyout/__test__/flyout.unit.test.ts#L27-L29

After losing focus or hover, my component animates out the content element using a svelte transition. Locally, the waitForElementToBeRemoved call resolves after some time (between 1000 and 2000 ms) that is far higher than the transition duration (300ms), but for some reason it looks like on GH Actions, the test fails even with a timeout as high as 5000ms.

Basically, I'm looking for some way to either completely disable transitions in a test environment, or reliably wait for them to conclude.

@bartektelec
Copy link

bartektelec commented Oct 14, 2022

Hi guys, any luck in fixing this one yet? I've been struggling with testing components that use svelte/transition for a while now. Any time I apply a transition directive to my elements the tests start to fail, I've tried mocking them like

vi.mock('svelte/transition', () => ({
  fly: () => ({
    delay: 0,
    duration: 0
  })
}));

but it doesn't solve it.

edit:

I figured out a hacky solution:
You will need to explicitly define an element to be rendered inside of your testing environment, and it cannot have a transition: directive applied to it. As transitions can't be currently set conditionally this is the only way I managed to disable transitions for tested components.

My component:

{#each $toasts as toast (toast.id)}
	{#if import.meta.env.VITEST}
		<div data-testid="alert">
    		{@html toast.message}
    	</div>
	{:else}
    	<div data-testid="alert" transition:fly>
    		{@html toast.message}
    	</div>
	{/if}
{/each}

@Hagendorn
Copy link
Member

Hagendorn commented Nov 21, 2022

I also run in this issue and the root cause of this issue is that Svelte needs requestAnimationFrame. When using vitest with happy-dom you can use whenAsyncComplete before you try to read some stuff with testing libary:

await happyDOM.whenAsyncComplete()

await screen.findByText(`some text`)

For vitest and jsdom you need some custom fake for that. I was not able to use a fake timer implementation, but this is a fake implementation which worked with my examples:

let original: (callback: FrameRequestCallback) => number

function fakeRaf(fn: FrameRequestCallback): number {
  return window.setTimeout(() => fn(Date.now()), 16)
}

export function use() {
  original = window.requestAnimationFrame
  window.requestAnimationFrame = fakeRaf
}

export function restore() {
  window.requestAnimationFrame = original
}

Now you need to use the fake in your tests for example use it in the setup and teardown:

beforeEach(() => {
  rafFaker.use()
})

afterEach(() => {
  rafFaker.restore()
})

@FilipSzutkowski
Copy link

Thank you @Hagendorn. I used the second example you provided and it resolved the issues I had with Vitest and Svelte transitions.

It was actually a hell of a process to get down to, as the tests on components using transitions were just failing. As I dug dipper I discovered that the components were stuck in a kind of between state from before and after the transition.

I have this component which has a part of the template wrapped in a #if. The #if is based on a boolean prop's value which is called isOpen. As I was debugging, I finally decided to just render isOpens value in two places, before and after opening the #if. To my surprise, the result from PrettyDOM showed that isOpen rendered falsebefore the #if, while it rendered true just a few lines after, in the content that was wrapped in the #if.

@alexlafroscia
Copy link

alexlafroscia commented Mar 15, 2023

I ran into a similar issue myself, where an element rendered inside of an {#if used a transition to animate out of the DOM. waitForElementToBeRemoved would almost always time out (though, randomly, I saw it work perfectly maybe 3-4 times out of hundreds of runs).

It seems to me like the problem has something to do with JSDOM's requestAnimationFrame implementation. If I use happy-dom instead, the tests pass just fine.

I was able to resolve my issue by stubbing requestAnimationFrame following the pattern recommended by @Hagendorn. Using Vitest, it looks like

beforeEach(() => {
  vi.stubGlobal('requestAnimationFrame', (fn) => {
    return window.setTimeout(() => fn(Date.now()), 16);
  });
});

afterEach(() => {
  vi.unstubAllGlobals();
});

The real head-scratcher here, for me, is that these tests were previously running using JSDOM and Jest without error; it's only in moving to Vitest did this issue present itself. Even if I use the version of JSDOM that Jest was using, rather than a modernized version, this issue remains. So maybe it has something to do with how Vitest sets up JSDOM specifically? I'm honestly not sure yet. The issue is complex enough and touches enough different projects that I'm not sure where exactly to bring this up; I was linked to this issue by @bartektelec from the Svelte Discord, but I'm not sure it's actually a @testing-library/svelte bug.

I have a minimal reproduction of my issue that can be found here:

https://github.com/alexlafroscia/Svelte-Vitest-Transition-Testing-Bug-Reproduction

It contains a failing test around a waitForElementToBeRemoved that times out when using JSDOM, due to the element in question transitioning out rather than being removed directly.

@austinworks
Copy link

@alexlafroscia your fix is awesome, i can't believe how much time i spent searching for "vitest svelte transitions" before I came to this repo to search the issue tracker

@alexlafroscia
Copy link

@austinworks you and me both! It took me hours to get to the bottom of this while transitioning from Jest to Vitest, and I only even got to this point by getting very lucky to find a thread on the Vitest Discord server that sounded similar, and then extra lucky that the author of that thread pointed me in this direction.

It's a tricky thing to hunt down a solution to, because I believe the bug is actually the interaction between libraries here. Something about this library+JSDOM+the specific test runner seems to be the problem.

For some reason, this all works fine with Jest+JSDOM, using the same version of JSDOM and this library that I'm now using with Vitest. It works fine in Jest but breaks with Vitest. I have no idea why Vitest vs. Jest would matter here; I looked into how both of them instantiate JSDOM and couldn't find any significant difference there 🤷

Copy link

🎉 This issue has been resolved in version 4.2.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
Projects
None yet
7 participants