Skip to content

Commit

Permalink
refactor(web): L10n provider does not rely on L10nClient
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Sep 25, 2024
1 parent d604230 commit 8c50574
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 76 deletions.
17 changes: 10 additions & 7 deletions web/src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ import { InstallationPhase } from "./types/status";

jest.mock("~/client");

jest.mock("~/api/l10n", () => ({
...jest.requireActual("~/api/l10n"),
fetchConfig: jest.fn().mockResolvedValue({
uiKeymap: "en",
uiLocale: "en_us",
}),
updateConfig: jest.fn(),
}));

// list of available products
let mockProducts;
let mockSelectedProduct;
Expand Down Expand Up @@ -88,13 +97,7 @@ describe("App", () => {
// setting the language through a cookie
document.cookie = "agamaLang=en-us; path=/;";
createClient.mockImplementation(() => {
return {
l10n: {
getUIKeymap: jest.fn().mockResolvedValue("en"),
getUILocale: jest.fn().mockResolvedValue("en_us"),
setUILocale: jest.fn().mockResolvedValue("en_us"),
},
};
return {};
});

mockProducts = [
Expand Down
1 change: 0 additions & 1 deletion web/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ const createClient = (url) => {

const createDefaultClient = async () => {
const httpUrl = new URL(window.location.toString());
httpUrl.hash = "";
return createClient(httpUrl);
};

Expand Down
29 changes: 0 additions & 29 deletions web/src/client/status.js

This file was deleted.

31 changes: 16 additions & 15 deletions web/src/context/installerL10n.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@

import React, { useCallback, useEffect, useState } from "react";
import { useCancellablePromise, locationReload, setLocationSearch } from "~/utils";
import { useInstallerClient } from "./installer";
import { useInstallerClientStatus } from "./installer";
import agama from "~/agama";
import supportedLanguages from "~/languages.json";
import { fetchConfig, updateConfig } from "~/api/l10n";

const L10nContext = React.createContext(null);

Expand Down Expand Up @@ -215,31 +216,31 @@ function reload(newLanguage) {
* @see useInstallerL10n
*/
function InstallerL10nProvider({ children }) {
const client = useInstallerClient();
const { connected } = useInstallerClientStatus();
const [language, setLanguage] = useState(undefined);
const [keymap, setKeymap] = useState(undefined);
const [backendPending, setBackendPending] = useState(false);
const { cancellablePromise } = useCancellablePromise();

const storeInstallerLanguage = useCallback(
async (newLanguage) => {
if (!client) {
if (!connected) {
setBackendPending(true);
return false;
}

const locale = await cancellablePromise(client.l10n.getUILocale());
const currentLanguage = languageFromLocale(locale);
const config = await cancellablePromise(fetchConfig());
const currentLanguage = languageFromLocale(config.uiLocale);

if (currentLanguage !== newLanguage) {
// FIXME: fallback to en-US if the language is not supported.
await cancellablePromise(client.l10n.setUILocale(languageToLocale(newLanguage)));
await cancellablePromise(updateConfig({ uiLocale: languageToLocale(newLanguage) }));
return true;
}

return false;
},
[client, cancellablePromise],
[connected, cancellablePromise],
);

const changeLanguage = useCallback(
Expand Down Expand Up @@ -270,29 +271,29 @@ function InstallerL10nProvider({ children }) {

const changeKeymap = useCallback(
async (id) => {
if (!client) return;
if (!connected) return;

setKeymap(id);
client.l10n.setUIKeymap(id);
updateConfig({ uiKeymap: id });
},
[setKeymap, client],
[setKeymap, connected],
);

useEffect(() => {
if (!language) changeLanguage();
}, [changeLanguage, language]);

useEffect(() => {
if (!client || !backendPending) return;
if (!connected || !backendPending) return;

storeInstallerLanguage(language);
setBackendPending(false);
}, [client, language, backendPending, storeInstallerLanguage]);
}, [connected, language, backendPending, storeInstallerLanguage]);

useEffect(() => {
if (!client) return;
client.l10n.getUIKeymap().then(setKeymap);
}, [setKeymap, client]);
if (!connected) return;
fetchConfig().then((c) => setKeymap(c.uiKeymap));
}, [setKeymap, connected]);

const value = { language, changeLanguage, keymap, changeKeymap };

Expand Down
51 changes: 29 additions & 22 deletions web/src/context/installerL10n.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ import { InstallerL10nProvider } from "~/context/installerL10n";
import { InstallerClientProvider } from "./installer";
import * as utils from "~/utils";

const getUILocaleFn = jest.fn().mockResolvedValue();
const setUILocaleFn = jest.fn().mockResolvedValue();
const mockFetchConfigFn = jest.fn();
const mockUpdateConfigFn = jest.fn();

jest.mock("~/context/installer", () => ({
...jest.requireActual("~/context/installer"),
useInstallerClientStatus: () => ({ connected: true, error: false }),
}));

jest.mock("~/api/l10n", () => ({
...jest.requireActual("~/api/l10n"),
fetchConfig: () => mockFetchConfigFn(),
updateConfig: (config) => mockUpdateConfigFn(config),
}));

const client = {
onConnect: jest.fn(),
onDisconnect: jest.fn(),
l10n: {
getUILocale: getUILocaleFn,
setUILocale: setUILocaleFn,
getUIKeymap: jest.fn().mockResolvedValue("en"),
},
};

jest.mock("~/languages.json", () => ({
Expand Down Expand Up @@ -73,6 +79,7 @@ describe("InstallerL10nProvider", () => {
jest.spyOn(utils, "locationReload").mockImplementation(utils.noop);
jest.spyOn(utils, "setLocationSearch");

mockUpdateConfigFn.mockResolvedValue(true);
delete window.navigator;
window.navigator = { languages: ["es-es", "cs-cz"] };
});
Expand All @@ -91,7 +98,7 @@ describe("InstallerL10nProvider", () => {
describe("when the language is already set", () => {
beforeEach(() => {
document.cookie = "agamaLang=en-us; path=/;";
getUILocaleFn.mockResolvedValueOnce("en_US.UTF-8");
mockFetchConfigFn.mockResolvedValue({ uiLocale: "en_US.UTF-8" });
});

it("displays the children content and does not reload", async () => {
Expand All @@ -113,8 +120,8 @@ describe("InstallerL10nProvider", () => {
describe("when the language is set to an unsupported language", () => {
beforeEach(() => {
document.cookie = "agamaLang=de-de; path=/;";
getUILocaleFn.mockResolvedValueOnce("de_DE.UTF-8");
getUILocaleFn.mockResolvedValueOnce("es_ES.UTF-8");
mockFetchConfigFn.mockResolvedValueOnce({ uiLocale: "de_DE.UTF-8" });
mockFetchConfigFn.mockResolvedValue({ uiLocale: "es_ES.UTF-8" });
});

it("uses the first supported language from the browser", async () => {
Expand All @@ -138,7 +145,7 @@ describe("InstallerL10nProvider", () => {
);

await waitFor(() => screen.getByText("hola"));
expect(setUILocaleFn).toHaveBeenCalledWith("es_ES.UTF-8");
expect(mockUpdateConfigFn).toHaveBeenCalledWith({ uiLocale: "es_ES.UTF-8" });
});
});

Expand All @@ -147,7 +154,7 @@ describe("InstallerL10nProvider", () => {
// Ensure both, UI and backend mock languages, are in sync since
// client.setUILocale is mocked too.
// See navigator.language in the beforeAll at the top of the file.
getUILocaleFn.mockResolvedValue("es_ES.UTF-8");
mockFetchConfigFn.mockResolvedValue({ uiLocale: "es_ES.UTF-8" });
});

it("sets the preferred language from browser and reloads", async () => {
Expand Down Expand Up @@ -210,7 +217,7 @@ describe("InstallerL10nProvider", () => {
describe("when the language is already set to 'cs-cz'", () => {
beforeEach(() => {
document.cookie = "agamaLang=cs-cz; path=/;";
getUILocaleFn.mockResolvedValueOnce("cs_CZ.UTF-8");
mockFetchConfigFn.mockResolvedValue({ uiLocale: "cs_CZ.UTF-8" });
});

it("displays the children content and does not reload", async () => {
Expand All @@ -224,7 +231,7 @@ describe("InstallerL10nProvider", () => {

// children are displayed
await screen.findByText("ahoj");
expect(setUILocaleFn).not.toHaveBeenCalled();
expect(mockUpdateConfigFn).not.toHaveBeenCalled();

expect(document.cookie).toMatch(/agamaLang=cs-cz/);
expect(utils.locationReload).not.toHaveBeenCalled();
Expand All @@ -235,9 +242,9 @@ describe("InstallerL10nProvider", () => {
describe("when the language is set to 'en-us'", () => {
beforeEach(() => {
document.cookie = "agamaLang=en-us; path=/;";
getUILocaleFn.mockResolvedValueOnce("en_US");
getUILocaleFn.mockResolvedValueOnce("cs_CZ");
setUILocaleFn.mockResolvedValue();
mockFetchConfigFn.mockResolvedValueOnce({ uiLocale: "en_US" });
mockFetchConfigFn.mockResolvedValueOnce({ uiLocale: "cs_CZ" });
mockUpdateConfigFn.mockResolvedValue();
});

it("sets the 'cs-cz' language and reloads", async () => {
Expand All @@ -261,15 +268,15 @@ describe("InstallerL10nProvider", () => {
);

await waitFor(() => screen.getByText("ahoj"));
expect(setUILocaleFn).toHaveBeenCalledWith("cs_CZ.UTF-8");
expect(mockUpdateConfigFn).toHaveBeenCalledWith({ uiLocale: "cs_CZ.UTF-8" });
});
});

describe("when the language is not set", () => {
beforeEach(() => {
getUILocaleFn.mockResolvedValueOnce("en_US.UTF-8");
getUILocaleFn.mockResolvedValueOnce("cs_CZ.UTF-8");
setUILocaleFn.mockResolvedValue();
mockFetchConfigFn.mockResolvedValueOnce({ uiLocale: "en_US.UTF-8" });
mockFetchConfigFn.mockResolvedValue({ uiLocale: "cs_CZ.UTF-8" });
mockUpdateConfigFn.mockResolvedValue();
});

it("sets the 'cs-cz' language and reloads", async () => {
Expand All @@ -293,7 +300,7 @@ describe("InstallerL10nProvider", () => {
);

await waitFor(() => screen.getByText("ahoj"));
expect(setUILocaleFn).toHaveBeenCalledWith("cs_CZ.UTF-8");
expect(mockUpdateConfigFn).toHaveBeenCalledWith({ uiLocale: "cs_CZ.UTF-8" });
});
});
});
Expand Down
4 changes: 4 additions & 0 deletions web/src/queries/l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ const useL10n = () => {
const selectedLocale = locales.find((l) => l.id === config.locales[0]);
const selectedKeymap = keymaps.find((k) => k.id === config.keymap);
const selectedTimezone = timezones.find((t) => t.id === config.timezone);
const uiLocale = locales.find((l) => l.id === config.uiLocale);
const uiKeymap = keymaps.find((k) => k.id === config.uiKeymap);

return {
locales,
Expand All @@ -112,6 +114,8 @@ const useL10n = () => {
selectedLocale,
selectedKeymap,
selectedTimezone,
uiLocale,
uiKeymap,
};
};

Expand Down
4 changes: 2 additions & 2 deletions web/src/types/l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ type LocaleConfig = {
/**
* Locale to be used in the UI.
*/
ui_locale?: string;
uiLocale?: string;
/**
* Locale to be used in the UI.
*/
ui_keymap?: string;
uiKeymap?: string;
};

export type { Keymap, Locale, Timezone, LocaleConfig };

0 comments on commit 8c50574

Please sign in to comment.