Skip to content

Commit

Permalink
[web] Fix tests mocking window.location
Browse files Browse the repository at this point in the history
Because it's no longer possible to mock some window properties since
jsdom 21.0.0 and it was updated to version 22.1.0 in previous commits.

See https://github.com/jsdom/jsdom/blob/master/Changelog.md#2100 and
jsdom/jsdom#3492
  • Loading branch information
dgdavid committed Oct 30, 2023
1 parent 9622e36 commit 62801ff
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 43 deletions.
3 changes: 2 additions & 1 deletion web/src/components/core/DBusError.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import React from "react";
import { Button, EmptyState, EmptyStateIcon, EmptyStateBody, EmptyStateHeader } from "@patternfly/react-core";
import { locationReload } from "~/utils";
import { _ } from "~/i18n";

import {
Expand All @@ -35,7 +36,7 @@ const ErrorIcon = () => <Icon name="error" className="icon-big" />;

// TODO: an example
const ReloadAction = () => (
<Button size="lg" variant="primary" onClick={() => location.reload()}>
<Button size="lg" variant="primary" onClick={() => locationReload()}>
{/* TRANSLATORS: button label */}
{_("Reload")}
</Button>
Expand Down
14 changes: 4 additions & 10 deletions web/src/components/core/DBusError.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import React from "react";

import { screen } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import * as utils from "~/utils";

import { DBusError } from "~/components/core";

Expand All @@ -35,20 +36,13 @@ describe("DBusError", () => {
});

it("calls location.reload when user clicks on 'Reload'", async () => {
jest.spyOn(utils, "locationReload").mockImplementation(utils.noop);

const { user } = plainRender(<DBusError />, { layout: true });

const reloadButton = await screen.findByRole("button", { name: /Reload/i });

// Mock location.reload
// https://remarkablemark.org/blog/2021/04/14/jest-mock-window-location-href
const { location } = window;
delete window.location;
window.location = { reload: jest.fn() };

await user.click(reloadButton);
expect(window.location.reload).toHaveBeenCalled();

// restore windows.location
window.location = location;
expect(utils.locationReload).toHaveBeenCalled();
});
});
8 changes: 4 additions & 4 deletions web/src/context/l10n.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// @ts-check

import React, { useCallback, useEffect, useState } from "react";
import { useCancellablePromise } from "~/utils";
import { useCancellablePromise, locationReload, setLocationSearch } from "~/utils";
import cockpit from "../lib/cockpit";
import { useInstallerClient } from "./installer";

Expand Down Expand Up @@ -174,11 +174,11 @@ function reload(newLanguage) {
const query = new URLSearchParams(window.location.search);
if (query.has("lang") && query.get("lang") !== newLanguage) {
query.set("lang", newLanguage);
// Calling search() with a different value makes the browser to navigate
// Setting location search with a different value makes the browser to navigate
// to the new URL.
window.location.search = query.toString();
setLocationSearch(query.toString());
} else {
window.location.reload();
locationReload();
}
}

Expand Down
50 changes: 23 additions & 27 deletions web/src/context/l10n.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { render, waitFor, screen } from "@testing-library/react";

import { L10nProvider } from "~/context/l10n";
import { InstallerClientProvider } from "./installer";
import * as utils from "~/utils";

const getUILanguageFn = jest.fn().mockResolvedValue();
const setUILanguageFn = jest.fn().mockResolvedValue();
Expand Down Expand Up @@ -72,24 +73,14 @@ const TranslatedContent = () => {
};

describe("L10nProvider", () => {
// remember the original object, we need to temporarily replace it with a mock
const origLocation = window.location;
const origNavigator = window.navigator;

// mock window.location.reload and search
beforeAll(() => {
delete window.location;
window.location = { reload: jest.fn(), search: "" };
jest.spyOn(utils, "locationReload").mockImplementation(utils.noop);
jest.spyOn(utils, "setLocationSearch");

delete window.navigator;
window.navigator = { languages: ["es-es", "cs-cz"] };
});

afterAll(() => {
window.location = origLocation;
window.navigator = origNavigator;
});

// remove the Cockpit language cookie after each test
afterEach(() => {
// setting a cookie with already expired date removes it
Expand Down Expand Up @@ -117,8 +108,7 @@ describe("L10nProvider", () => {
// children are displayed
await screen.findByText("hello");

expect(window.location.search).toEqual("");
expect(window.location.reload).not.toHaveBeenCalled();
expect(utils.locationReload).not.toHaveBeenCalled();
});
});

Expand All @@ -136,16 +126,16 @@ describe("L10nProvider", () => {
</InstallerClientProvider>
);

await waitFor(() => expect(window.location.reload).toHaveBeenCalled());
await waitFor(() => expect(utils.locationReload).toHaveBeenCalled());

// reload the component
// renders again after reloading
render(
<InstallerClientProvider client={client}>
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => screen.getByText("hola"));

await waitFor(() => screen.getByText("hola"));
expect(setUILanguageFn).toHaveBeenCalledWith("es_ES");
});
});
Expand All @@ -164,8 +154,10 @@ describe("L10nProvider", () => {
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => expect(window.location.reload).toHaveBeenCalled());

await waitFor(() => expect(utils.locationReload).toHaveBeenCalled());

// renders again after reloading
render(
<InstallerClientProvider client={client}>
<L10nProvider><TranslatedContent /></L10nProvider>
Expand All @@ -185,8 +177,10 @@ describe("L10nProvider", () => {
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => expect(window.location.reload).toHaveBeenCalled());

await waitFor(() => expect(utils.locationReload).toHaveBeenCalled());

// renders again after reloading
render(
<InstallerClientProvider client={client}>
<L10nProvider><TranslatedContent /></L10nProvider>
Expand All @@ -200,7 +194,7 @@ describe("L10nProvider", () => {

describe("when the URL query parameter is set to '?lang=cs-CZ'", () => {
beforeEach(() => {
window.location.search = "?lang=cs-CZ";
history.replaceState(history.state, null, `http://localhost/?lang=cs-CZ`);
});

describe("when the Cockpit language is already set to 'cs-cz'", () => {
Expand All @@ -221,8 +215,8 @@ describe("L10nProvider", () => {
expect(setUILanguageFn).not.toHaveBeenCalled();

expect(document.cookie).toEqual("CockpitLang=cs-cz");
expect(window.location.reload).not.toHaveBeenCalled();
expect(window.location.search).toEqual("?lang=cs-CZ");
expect(utils.locationReload).not.toHaveBeenCalled();
expect(utils.setLocationSearch).not.toHaveBeenCalled();
});
});

Expand All @@ -240,16 +234,17 @@ describe("L10nProvider", () => {
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => expect(window.location.search).toEqual("lang=cs-cz"));

// reload the component
await waitFor(() => expect(utils.setLocationSearch).toHaveBeenCalledWith("lang=cs-cz"));

// renders again after reloading
render(
<InstallerClientProvider client={client}>
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => screen.getByText("ahoj"));

await waitFor(() => screen.getByText("ahoj"));
expect(setUILanguageFn).toHaveBeenCalledWith("cs_CZ");
});
});
Expand All @@ -267,16 +262,17 @@ describe("L10nProvider", () => {
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => expect(window.location.search).toEqual("lang=cs-cz"));

await waitFor(() => expect(utils.setLocationSearch).toHaveBeenCalledWith("lang=cs-cz"));

// reload the component
render(
<InstallerClientProvider client={client}>
<L10nProvider><TranslatedContent /></L10nProvider>
</InstallerClientProvider>
);
await waitFor(() => screen.getByText("ahoj"));

await waitFor(() => screen.getByText("ahoj"));
expect(setUILanguageFn).toHaveBeenCalledWith("cs_CZ");
});
});
Expand Down
34 changes: 33 additions & 1 deletion web/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,44 @@ const hex = (value) => {
*/
const toValidationError = (issue) => ({ message: issue.description });

/**
* Wrapper around window.location.reload
* @function
*
* It's needed mainly to ease testing because we can't override window in jest with jsdom anymore
*
* See below links
* - https://github.com/jsdom/jsdom/blob/master/Changelog.md#2100
* - https://github.com/jsdom/jsdom/issues/3492
*/
const locationReload = () => {
window.location.reload();
};

/**
* Wrapper around window.location.search setter
* @function
*
* It's needed mainly to ease testing as we can't override window in jest with jsdom anymore
*
* See below links
* - https://github.com/jsdom/jsdom/blob/master/Changelog.md#2100
* - https://github.com/jsdom/jsdom/issues/3492
*
* @param {string} query
*/
const setLocationSearch = (query) => {
window.location.search = query;
};

export {
noop,
partition,
classNames,
useCancellablePromise,
useLocalStorage,
hex,
toValidationError
toValidationError,
locationReload,
setLocationSearch
};

0 comments on commit 62801ff

Please sign in to comment.