svelte-testing-library icon indicating copy to clipboard operation
svelte-testing-library copied to clipboard

Failed tests don't remove element from DOM

Open chrisolsen opened this issue 3 years ago • 9 comments

Because my svelte component is being compiled to a web component I need to do some additional validation on passed in attributes within the onMount function. If I throw an exception for an invalid attribute type the element isn't removed from the DOM and all later tests then fail due to the findByRole method then finding more than one element.

  onMount(() => {
    if (!isButtonType(type)) {
      throw "Invalid button type";
    }
    if (!isSize(size)) {
      throw "Invalid button size";
    }
    if (!isVariant(variant)) {
      throw "Invalid button variant";
    }
  })

  describe("size", () => {
    ["compact", "foo"].forEach(size => {
      it(`should render ${size} size`, async () => {
        const { findByRole } = render(GoAButton, { size });
        const button = await findByRole("button");

        expect(button).toBeTruthy();
        expect(button.className).toContain(size);
      });
    });
  });

Errors raised image

I have tried calling the cleanup method in an afterEach, but that didn't change anything.

chrisolsen avatar May 26 '22 16:05 chrisolsen

Hi, I had a similar issue previously and believe this will be addressed properly by utilizing screen, which is the recommended practice.

Import screen from @testing-library/svelte and update your test as follows:

  describe("size", () => {
    ["compact", "foo"].forEach(size => {
      it(`should render ${size} size`, () => {
        render(GoAButton, { size });
        const button = screen.findByRole("button");

        expect(button).toBeTruthy();
        expect(button.className).toContain(size);
      });
    });
  });

Pulling queries directly out of the render should also be avoided. There are certain circumstances where you will retrieve a query function that is unusable.

ObieMunoz avatar Jul 15 '22 21:07 ObieMunoz

Hello, I am also seeing an issue with this. It appears to happen with async test cases where the test throws an exception. I set up a simple problem case to reproduce it with a standard build of sveltekit. Maybe someone can take a look?

https://github.com/austinworks/render-problem-example

austinworks avatar Dec 30 '22 01:12 austinworks

We're you able to find any workaround for your issue @austinworks ?

canastro avatar Apr 12 '23 14:04 canastro

Seems like a legit screen pollution issue, even happens when using the { container } or { getByRole } returned from the render within the test.

best I've been able to figure out is wrapping the target version of the render in an identifier and then using queries within the test to scope it. I added a branch to demo this: https://github.com/austinworks/render-problem-example/blob/selector_workaround/src/example/CompTest.test.ts

austinworks avatar Apr 17 '23 18:04 austinworks

@yanick I dug into this and can confirm that this is, indeed, a cleanup bug in svelte-testing-library. It is difficult to reproduce in our own test suite due to #222, but can be done with some of the trickery mentioned in that thread.

  1. By default, render will add a div to the body element:

https://github.com/testing-library/svelte-testing-library/blob/c0ff791388f7230a492143fac5daad4d2529920f/src/pure.js#L26

  1. Then, it will create the Svelte component:

https://github.com/testing-library/svelte-testing-library/blob/c0ff791388f7230a492143fac5daad4d2529920f/src/pure.js#L57-L60

  1. Then, it will add the component and <div> to its internal cache:

https://github.com/testing-library/svelte-testing-library/blob/c0ff791388f7230a492143fac5daad4d2529920f/src/pure.js#L62

  1. Finally, it will read from the cache to cleanup

https://github.com/testing-library/svelte-testing-library/blob/c0ff791388f7230a492143fac5daad4d2529920f/src/pure.js#L108-L110

If the component throws in step (2) (or internals throw between steps (1) and (2)), the target will never be added to the container cache and thus, it will never get cleaned up.

For fixing the issues, there's a lot of suspect-looking stuff going on in the code in question. I think this might be a case of "simplify to fix"

  • appendChild side-effect in the target initializer
  • No way to differentiate between a managed div or an explicitly provided target, which may remove a passed in target from the DOM during cleanup
  • Multiple caches with components going into both caches
  • Repetition between render and rerender

mcous avatar Jan 24 '24 11:01 mcous

Thanks for looking into this.

austinworks avatar Jan 24 '24 17:01 austinworks

Repetition between render and rerender

I think that will be alleviated when we merge https://github.com/testing-library/svelte-testing-library/pull/210, as rerender will no longer destroy the component, and will be simplified by a lot.

No way to differentiate between a managed div or an explicitly provided target

I have to think a little more before I say anything silly, but perhaps we might do something as simple as add a __stl-wrapper class to the divs we are created, to provide an easy to recognize (and querySelector) them.

yanick avatar Jan 25 '24 16:01 yanick

I think that will be alleviated when we merge #210, as rerender will no longer destroy the component

Nice, yeah that should help!

perhaps we might do something as simple as add a __stl-wrapper class to the divs

Yeah, something like this feels pretty resilient. I was planning to peep the React and Vue libs to see what they do and check if there's an alignment opportunity

mcous avatar Jan 25 '24 20:01 mcous

:tada: This issue has been resolved in version 4.2.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Feb 13 '24 18:02 github-actions[bot]