10

I have written unit test using jest version 26.0.0 with react-testing-library and node version 14.2.0. Below is my React functional component which has a button, on clicking makes an ajax call to download a report file.

const ReportListTable = () => {
    const { downloadReports } = useReports();

    const onClickDownload = () => {
        let fileName = `Report.zip`;
        downloadReports()
            .then((response) => response.arrayBuffer())
            .then((data) => {
                const blob = new Blob([data], { type: "octet/stream", endings: "native" });
                const url = window.URL.createObjectURL(blob);
                const anchor = document.createElement("a");
                anchor.setAttribute("href", url);
                anchor.setAttribute("download", fileName);
                anchor.click();
                window.URL.revokeObjectURL(url);
            })
            .catch((error?: Error | Response) => {
                if (error instanceof Error) {
                    message.error(error?.message);
                }
            });
    };

    return (
        <div>
            <Button data-testid="download-btn" onClick={onClickDownload}>
                Download
            </Button>
        </div>
    );
};

And I have below test suite where the createObjectURL and revokeObjectURL before the test runs.

describe("Test Reports List Table component", () => {
    const { URL } = window;

    beforeAll(() => {
        window.URL.createObjectURL = jest.fn();
        window.URL.revokeObjectURL = jest.fn();

        jest.spyOn(useReportsHook, "useReports").mockReturnValue(useReportsMockValue);
    });

    afterAll(() => {
        window.URL = URL;
        jest.clearAllMocks();
    });

    it("should handle row button enabled and download on button clicked", async () => {
        jest.spyOn(useReportsContextHook, "useReportsContext").mockReturnValue([{ checkedIds: ["report_1"] }, jest.fn]);
        const { asFragment } = render(<ReportListTable />);
        
        fireEvent.click(await screen.getByTestId(elements.downloadTestId));
        await waitFor(() => expect(window.URL.createObjectURL).toHaveBeenCalled());
        await waitFor(() => expect(window.URL.revokeObjectURL).toHaveBeenCalled());
        expect(asFragment).toMatchSnapshot();
    });
});

The test case is passed however it throws the below error.

  console.error
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/Users/user/Documents/lydia-github/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/Users/user/Documents/lydia-github/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
        at exports.navigate (/Users/user/Documents/lydia-github/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
        at Timeout._onTimeout (/Users/user/Documents/lydia-github/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
        at listOnTimeout (internal/timers.js:549:17)
        at processTimers (internal/timers.js:492:7) undefined

I tried to mock the location object after referring this thread How to fix Error: Not implemented: navigation (except hash changes), however it didn't help. I need help to resolve this error.

Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
jagadish c
  • 241
  • 3
  • 8
  • Not an answer, but I have a similar situation for which I'm looking for a decent resolution. I've found that the error message is not emitted when the `anchor.click();` line is removed (or rather the equivalent line in my code). Have you tried selectively commenting out the business logic and finding which line emits the error in your case? – MrSpaceman Jun 18 '21 at 16:43

2 Answers2

20

I've worked out the problem:

JSDom doesn't like you to do navigation - cf., https://github.com/jsdom/jsdom/issues/2112#issuecomment-359297866

The issue we have here is that the code is performing a click event on an anchor:

const anchor = document.createElement("a");
anchor.setAttribute("href", url);
anchor.setAttribute("download", fileName);
anchor.click();

A click on an anchor is a navigation, usually, which I presume JSDom is taking exception to.

To solve the annoying logging, rather than mocking the location, mock the click():

HTMLAnchorElement.prototype.click = jest.fn();

This worked for me.

Incidentally, I also had to mock the window.URL.createObjectUrl call as I was getting:

TypeError: window.URL.createObjectURL is not a function

...so:

global.window.URL.createObjectURL = jest.fn();

Hope this works for you.

MrSpaceman
  • 404
  • 6
  • 19
  • 4
    Another approach is to listen for and cancel the click event in the test, to prevent the JSDom navigation occurring: `anchor.addEventListener("click", (e) => { e.preventDefault(); })` – Hal Jan 24 '23 at 04:46
0

This warning appears during tests when you try to navigate, which is not (yet) supported by JSDOM (See this issue).

Generally you can just ignore this warning, unless you intend to run your tests in a browser or something. It's a test-only warning, and doesn't actually indicate a problem in your code; you're just running into a feature that JSDOM hasn't implemented yet.

There are several ways to address it, if the warning bothers you (see above, or this post), depending on how precisely you're navigating.

Andrew
  • 3,825
  • 4
  • 30
  • 44