solidjs / solid-testing-library

Simple and complete Solid testing utilities that encourage good testing practices.
MIT License
198 stars 18 forks source link

Cannot query children in `<Portal>`s #62

Open gBasil opened 4 months ago

gBasil commented 4 months ago

The query commands (getByRole, getByTestId, etc.) fail to pick up children of <Portal>s.


The children of <Portal>s get rendered in a separate <div> from the content of the render call. The various query commands seem to use the container element, which is the first <div>, and fail to pick up anything in a portal:

import { expect, test } from 'vitest';
import { render } from '@solidjs/testing-library';
import { Portal } from 'solid-js/web';

test('Portal test', () => {
  const { getByTestId, debug } = render(() => <>
    <p data-testid='one'>One</p>
    <Portal>
      <p data-testid='two'>Two</p>
    </Portal>
  </>);

  getByTestId('one'); // Succeeds
  getByTestId('two'); // Throws an error

  debug(); // Prints the following:
  /*
    <body>
      <div>
        <p data-testid="one">
          One
        </p>

      </div>
      <div>
        <p data-testid="two">
          Two
        </p>
      </div>
    </body>
  */
});

One workaround is to use the baseElement property with a query selector, i.e. baseElement.querySelector('[data-testid="two"]'). However, Solid Testing Library reuses the <body> between render()s:

test('aaa', () => {
  render(() => <p>test 1</p>);
  render(() => <p>test 2</p>);
  const res = render(() => <p>test 3</p>);

  res.debug(); // Prints out the following:
  /*
    <body>
      <div>
        <p>test 1</p>
      </div>
      <div>
        <p>test 2</p>
      </div>
      <div>
        <p>test 3</p>
      </div>
    </body>
  */
});

So, if you have two tests in one file, they will both be rendered in the same document, and so you'll likely run into issues when using baseElement.querySelector, as it might select elements from a different test.

gBasil commented 4 months ago

When using Vitest, adding this to a file solves the duplication issue for it and allows you to use baseElement.querySelector safely:

import { afterEach } from 'vitest';
import { cleanup } from '@solidjs/testing-library';

afterEach(() => {
  cleanup();
});

However, this might break (I haven't tested it extensively) when rendering multiple times in one test(), as you'd have to call it manually in that case.

atk commented 4 months ago

You don't need to cleanup afterwards, this is already done automatically. However, unless you choose isolation: true in the testing config, tests might influence each other.

gBasil commented 4 months ago

You don't need to cleanup afterwards, this is already done automatically.

I saw that in the docs somewhere, but it didn't seem to work properly, and I had to call it manually.

Also, I'd rather not incur the slowdown of isolating all tests, just the ones in a specific file.

atk commented 4 months ago

I don't want to encourage using isolation of tests at all costs, just explain the mechanism. Your issue might be timing- or error-related (tests could be cleaned up slightly delayed or may not be cleaned up after failure).

artonio commented 4 months ago

it wont work with a Portal because when you use Portal it will render html attached to the body, you need to use:

document.body.querySelector('[data-testid="two"]');

PS: I had the same issue and Github Copilot Chat told me to do it this way

atk commented 4 months ago

Please use screen.get... accessible queries instead of CSS selectors.