1

In our single-page application, switching between pages is realized with window.location.hash. And I found a problem in React Testing Library unit-tests - triggering click on <a/> tag clears window.location.hash. For example, when I am on the "Info" page, my hash is equal to '#info'. And when I click on <a /> element, hash does not change. But in the tests hash becomes an empty string '' after doing same thing.

Here's a short test that doesn't work:

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('Test', async () => {
    window.location.hash = 'hash';
    render(<a className="some-class">Some text</a>);
    await userEvent.click(screen.getByText('Some text'));
    await new Promise(resolve => setTimeout(resolve, 30));
    expect(window.location.hash).toEqual('#hash');
});

And here's it error message:

Error: expect(received).toEqual(expected) // deep equality

Expected: "#hash"
Received: ""

The problem is with anchor tag. Test with the span tag works OK:

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('Test', async () => {
    window.location.hash = 'hash';
    render(<span className="some-class">Some text</span>); // <span /> instead of <a />
    await userEvent.click(screen.getByText('Some text'));
    await new Promise(resolve => setTimeout(resolve, 30));
    expect(window.location.hash).toEqual('#hash');
});

Question How can I avoid this behavior? I can't just get rid of a tags in the code, because most of the basic components is declared in another npm library.

EDIT 1 I don't really want to check location.hash. I want to check page's content, which is depends on location.hash value. And the real problem is: when location.hash becomes empty string some of the content disappears.

Yoskutik
  • 1,859
  • 2
  • 17
  • 43

1 Answers1

1

Based on this SO, this behavior its more like a jsdom issue. And to accomplish the expected value you should do something like this:

const oldLocation = window.location;

describe("matching cities to foods", () => {

  afterEach(() => {
    // AFTER EACH TEST, set it to the original object.
    window.location = oldLocation;
  });

  test("Test", async () => {
    // delete only for this test
    delete window.location;

    // Change only the hash option and keep the rest same as the original.
    window.location = { ...oldLocation, hash: "hash" };

    render(<a className="some-class">Some text</a>);

    await userEvent.click(screen.getByText("Some text"));

    await new Promise((resolve) => setTimeout(resolve, 30));

    expect(window.location.hash).toEqual("hash");
  });
});
Luis Paulo Pinto
  • 5,578
  • 4
  • 21
  • 35
  • That's instresting. But what if I need to change hash in the source code? I don't think that deleting `window.location` on the client side is a good idea – Yoskutik May 24 '22 at 16:03
  • Also, in your example you're checking that hash is equal to `hash` but in production it would be `#hash` – Yoskutik May 24 '22 at 16:07
  • An option related to delete window, is do something like the other SO - set the original value after each test - (i've updated the answer). – Luis Paulo Pinto May 24 '22 at 16:13
  • And related to hash, as you are mocking it, the expected value should be without the `#`, unless you set with it before. – Luis Paulo Pinto May 24 '22 at 16:15