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

Unable to include css while running tests in CRA? #10483

Open
petermarcoen opened this issue Feb 1, 2021 · 17 comments
Open

Unable to include css while running tests in CRA? #10483

petermarcoen opened this issue Feb 1, 2021 · 17 comments

Comments

@petermarcoen
Copy link

When I test a component containing a React Bootstrap Accordion component I cannot check whether or not the Accordion Cards are opened or closed. According to my tests they are always visible, even if they shouldn't.

I strongly suspect that this is because the bootstrap css is not considered.
The React Bootstrap Accordion component uses css classes to show or hide the content and I think that the test is not including this css, even though I import it in the test class.

The weird thing is, this does work in codesandbox:

https://codesandbox.io/s/ecstatic-platform-tt3on?file=/src/App.test.js

So I think this has something to do with CRA or webpack.
I tried the exact same thing as in the codesandbox in a brand new CRA app and I verified that the libraries are the same exact version (react-scripts 4.0.1).

Here are the relevant bits:

App.js

import React from "react";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Accordion>
        <Card>
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="0">
              Click me!
            </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey="0">
            <Card.Body>
              <Button>Secret button</Button>
            </Card.Body>
          </Accordion.Collapse>
        </Card>
        <Card>
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="1">
              Click me!
            </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey="1">
            <Card.Body>Hello! I'm another body</Card.Body>
          </Accordion.Collapse>
        </Card>
      </Accordion>
    </div>
  );
}

App.test.js

import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";

it("renders", async () => {
  render(<App />);

  // Succeeds
  expect(await screen.findByRole("button", { name: /Secret button/ }));
});

If I remove the import of the bootstrap.min.css in the file above the test will always succeed, even on codesandbox but with this import included it should fail which it does in codesandbox but not in my create-react-app

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 1, 2021

Hi @petermarcoen, I've had a look at the demo you provided (thanks for that) and you're right.

It looks like the test failure is actually correct. You need to first open the accordion before you can find the button with RTL.

it("renders", async () => {
  render(<App />);

  // Get the first header (button) and click to open the accordion
  const firstHeader = screen.getAllByText("Click me!")[0];
  fireEvent.click(firstHeader);

  // Fails only if the above import of bootstrap.min.css is there
  expect(await screen.findByRole("button", { name: /Secret button/ }));
});

You can create a test setup file and import your CSS there (so you don't have to do it per test).

Without the CSS, it looks like the below which is why the current test passes when you remove the CSS.
image

I hope this helps, please let me know if you need any further help here!

@petermarcoen
Copy link
Author

Hi @mrmckeb, thank you for taking a look at this.
So, yes, it should fail, I'm expecting it to fail but in my create-react-app it doesn't, on codesandbox it does.
I tried your suggestion of adding the css import in setupTests.js (even though I already added it in the test file itself) but still the test passes while it should fail.

Could you please take a look at my create-react-app example at https://github.com/petermarcoen/cra-css-problem?

The exact steps I took for creating this example are the following:

  1. npx create-react-app cra-css-problem
  2. cd cra-css-problem
  3. npm install react-bootstrap bootstrap
  4. Added import "bootstrap/dist/css/bootstrap.min.css"; to index.js
  5. Added import "bootstrap/dist/css/bootstrap.min.css"; to setupTests.js
  6. Replaced App.js with the App.js I had on codesandbox
  7. Replaced App.test.js with the App.test.js I had on codesandbox

When I start my app I see the Accordion collapsed as it should be.
When I run npm test my test passes while it should fail (it does fail on codesandbox).

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 1, 2021

Thanks @petermarcoen, sorry I misunderstood your original post.

I've reproduced this, and can see that when running this in both environments, I see CSS in the head on CodeSandbox, and not locally. I even pulled the CodeSandbox project and still saw the same result.

import React from "react";
import { render, screen, prettyDOM } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";
import "bootstrap/dist/css/bootstrap.css";

it("renders", async () => {
  render(<App />);

  console.log(prettyDOM(document));

  expect(await screen.findByRole("button", { name: /Secret button/ }));
});

I'll ask others in the team if they have any ideas, as I can't seem to find the cause here - and I'm not 100% sure on what the expected behaviour is.

@technikhil314
Copy link

technikhil314 commented Feb 2, 2021

I am still confused.

But in my opinion it will pass because bootstrap just hides your secret button (meaning its there in DOM but just hidden using css display/visibility or something). What you actually need is to check the visibility of your secret button. All you need to do it extent the jest's expect using jest-dom's extend-expect and check for toBeVisible.

Check how to setup jest-dom here if not done already

Check this sandbox I am searching for your secret button using testid and it passes the test. Seems something problematic with role. Not sure what the problem with role="button" is

NOTE: Bootstrap does not do conditional render.

@petermarcoen
Copy link
Author

petermarcoen commented Feb 3, 2021

Hi @technikhil314, thank you for your time.

In your example the test succeeds but I actually want this test to fail.
In my real app this accordion could be opened or closed, based on a certain condition, and I want to test if it is open or not.
Now, I can't seem to test for that since this test always succeeds so it is of no use to me.

This must be possible because in my codesandbox example it correctly marks the test as failed, since the element is not visible.

@technikhil314
Copy link

technikhil314 commented Feb 3, 2021

You are right you should check for .not.tobevisible. correct me if I am wrong.

And by fail I am assuming you are expecting the secret button not visible to user.

@petermarcoen
Copy link
Author

Hi @technikhil314, thanks for taking your time with this issue.
I tried your suggestion, now it always fails, it still thinks my button is visible even though it is not:

image

It does work correctly on Codesandbox:

https://codesandbox.io/s/tt3on?file=/src/App.test.js

image

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 3, 2021

@petermarcoen, I've been talking to the team and we're all confused by this, as none of us expected this to work.

For a long time, we've transformed CSS (and CSS modules) with this simple transform:
https://github.com/facebook/create-react-app/tree/master/packages/react-scripts/config/jest/cssTransform.js

I also noted the flag in the test script, but this again doesn't modify the behaviour above. I saw no difference with that flag on or off.

"test": "react-scripts test --env=jsdom"

We believe this difference is actually because CodeSandbox are running the tests in a browser, you can see jsdom downloading when you watch the Network tab in DevTools.

If you want to test proper CSS behaviour, I'd recommend looking at Puppeteer or Playwright (the latter supports Webkit). Both have Jest integrations, and there's also the new Playwright test runner. Another option would be Cypress.

That being said, if you have the time and are interested in trying to solve this for CRA, let me know!

@petermarcoen
Copy link
Author

Hi @mrmckeb, thank you for your effort!

As a workaround I now test for the CSS classnames themselves (see code below).
That works but is obviously not the ideal solution, it goes against testing-library's guiding principle of testing the application components in the way the user would use it.

Not sure if you want to close this issue or leave it open for a better solution in the future.

App.test.js

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";

it("renders a closed accordion by default", async () => {
  render(<App />);

  const collapse = screen.getByTestId("collapse");
  expect(collapse.classList).not.toContain("show");
});

it("renders a opened accordion when clicked", async () => {
  render(<App />);

  userEvent.click(screen.getAllByRole("button", { name: /Click me/ })[0]);

  const collapse = screen.getByTestId("collapse");
  await waitFor(() => expect(collapse.classList).toContain("show"));
});

App.js

import React from "react";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Accordion>
        <Card>
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="0">
              Click me!
            </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey="0" data-testid="collapse">
            <Card.Body>
              <Button>Secret button</Button>
            </Card.Body>
          </Accordion.Collapse>
        </Card>
        <Card>
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="1">
              Click me!
            </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey="1">
            <Card.Body>Hello! I'm another body</Card.Body>
          </Accordion.Collapse>
        </Card>
      </Accordion>
    </div>
  );
}

@technikhil314
Copy link

Hi @technikhil314, thanks for taking your time with this issue.
I tried your suggestion, now it always fails, it still thinks my button is visible even though it is not:

image

It does work correctly on Codesandbox:

https://codesandbox.io/s/tt3on?file=/src/App.test.js

image

Hmm I missed the line which says not.tobevisible is failing. Need to check how not.tobevisible is checking visibility of an elements

@technikhil314
Copy link

@petermarcoen @mrmckeb Its indeed an issue with jsdom it seem the cascade of css is not working well with @tsting-library/jest-dom package which is internally using jsdom.

See more here testing-library/jest-dom#209

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 4, 2021

We can leave this open for now @petermarcoen. I agree (as does the community) that it's less than ideal to test based on classnames...

It would be great to solve this in future - I'll see if I can make some time in the next weeks to rethink this.

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 4, 2021

Thanks for jumping in @technikhil314. I'll also take a look through RTL issues in the coming weeks and see if anything sticks out.

@technikhil314
Copy link

@mrmckeb I too would like to contribute to thie repo.It would be great if you can please give me some pointers to start looking into it?

@mrmckeb
Copy link
Contributor

mrmckeb commented Feb 5, 2021

Hi @technikhil314, that would be great. I haven't given it too much thought yet, but you can see here that we're simply throwing away the CSS today.
https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/jest/cssTransform.js

You can see the difference in the <head /> of each document when you run the above example locally or in codesandbox.

@technikhil314
Copy link

technikhil314 commented Feb 5, 2021

Seems like we need a css and scss transformer for jest. Although jest's cache will kick in but I feel it will hamper test runners performance. WDYT? @mrmckeb

@technikhil314
Copy link

Hey @mrmckeb

I changed the cssTransform.css file to this

// @remove-on-eject-begin
/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
// @remove-on-eject-end
'use strict';

// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html

module.exports = {
  process(fileContent) {
    console.log("object");
    const demo = `module.exports = (function demo() {
      const style = document.createElement("style");
      style.setAttribute("type", "text/css");
      style.innerHTML = "${fileContent.replace(/[\n]/g, " ")}";
      document.body.appendChild(style);
      return null;
  })()`;
    console.log(demo);
    return demo;
  },
  getCacheKey(fileContent, fileAbsolutePath) {
    // The output is always the same.
    return fileAbsolutePath;
  },
};

And its working for .css files need to work on .scss files.
I will appreciate any suggestion from your side on this.
visibility: hidden still wont work as there is some issue on jsdom about visibility:hidden, but at lease now style tags are getting rendered when jest renders a component.

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

No branches or pull requests

3 participants