MatthewHerbst / react-to-print

Print React components in the browser. Supports Chrome, Safari, Firefox and EDGE
MIT License
2.07k stars 221 forks source link

Is it possible to test? #597

Closed titenis closed 1 year ago

titenis commented 1 year ago

Hello, Any ideas how to test stuff written with this lib? I've been unsuccessfully trying to assert that window.printis called, but seems that is not possible. First of all - unable to mock/stub window.print in jest/vitest, since the mocking happens in parent window and the actual printing happens in child iframe window, which ignores mocks. Same thing with cypress, only stubing parent window print and the iframe is not even in the dom before the printing function is initiated. I could mock whole react-to-print module, but I dont see real value in such test.

MatthewHerbst commented 1 year ago

Hi! That's an interesting question. The thing that comes to mind for me is the print prop, which lets you pass in your own function to call instead of window.print. Could you pass your mock into that?

const mockedPrint = someMockingLib((iframe: HTMLIframeElement) => {
  // We're printing, do something
  // Can return a Promise here as well and `react-to-print` will wait for it to resolve
});

const handlePrint = useReactToPrint({
  // ...
  print: mockedPrint,
  // ...
});
titenis commented 1 year ago

But that means I would have to alter my app specifically for a test to work, which, if Im not wrong, is a testing anti pattern

MatthewHerbst commented 1 year ago

I would posit that you trying to test 3rd party code (react-to-print's internal call of window.print) is a testing anti pattern. You should test that you call react-to-print correctly. You shouldn't test that a 3rd party library works or doesn't. For that, feel free to make a PR adding tests to this repo ❤️ 🙏

titenis commented 1 year ago

That is also correct. Thanks

MatthewHerbst commented 1 year ago

Will close for now, but am very open to figuring out how to add better tests to this library. The way I currently test is to go through each example on Chrome, Firefox, and Safari, but of course that isn't fully comprehensive (I don't normally test on mobile devices for example)

daniel-butler commented 8 months ago

Just to be clear, the option provided here only works for unit tests. An integration test, like cypress, isn't able to inject props, so the option provided isn't feasible.

MatthewHerbst commented 8 months ago

@daniel-butler do you have any suggestions for how we can make testing in an integration framework such as Cypress easy? If there's some low-hanging fruit here I would be very happy to address it

daniel-butler commented 8 months ago

I haven't been able to find a working solution, but I am thinking the <iframe id="printWindow" ... print function needs to be stubbed. In general Cypress doesn't really support the print dialogue, so the solution without this great library is to stub the windows print function with the code below.

cy.stub(cy.state('window'), 'print', () => {
    // mock implementation
});
almarzn commented 2 months ago

I would posit that you trying to test 3rd party code (react-to-print's internal call of window.print) is a testing anti pattern. You should test that you call react-to-print correctly. You shouldn't test that a 3rd party library works or doesn't. For that, feel free to make a PR adding tests to this repo ❤️ 🙏

I believe you are confusing unit test with integration/e2e tests, or you are using broad overly generalized statement as an inflexible rule that doesn't actually answer the question. Fortunately ChatGPT has a working response to OP:

    // Set up a MutationObserver to listen for the iframe being added to the DOM
    cy.window().then((win) => {
      const observer = new MutationObserver((mutationsList, observer) => {
        for (const mutation of mutationsList) {
          if (mutation.type === "childList") {
            for (const node of mutation.addedNodes) {
              if (node.tagName === "IFRAME") {
                const iframeWindow = node.contentWindow;
                cy.stub(iframeWindow, "print").as("iframePrint");
                observer.disconnect(); // Stop observing once iframe is found and stubbed
                return;
              }
            }
          }
        }
      });

      observer.observe(win.document.body, { childList: true, subtree: true });
    });