From 2d5a94afac909934127a15804681ebe091105fb6 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Tue, 13 Feb 2024 11:55:48 +0100 Subject: [PATCH] Implement a utility for PackageIntl creation during tests (#34) --- .changeset/eight-bears-draw.md | 5 ++ src/packages/test-utils/README.md | 16 +++++- src/packages/test-utils/build.config.mjs | 2 +- src/packages/test-utils/react.tsx | 20 +++---- src/packages/test-utils/services.ts | 18 +----- src/packages/test-utils/utils.ts | 12 ---- src/packages/test-utils/vanilla.test.ts | 41 ++++++++++++++ src/packages/test-utils/vanilla.ts | 72 ++++++++++++++++++++++++ 8 files changed, 143 insertions(+), 43 deletions(-) create mode 100644 .changeset/eight-bears-draw.md delete mode 100644 src/packages/test-utils/utils.ts create mode 100644 src/packages/test-utils/vanilla.test.ts create mode 100644 src/packages/test-utils/vanilla.ts diff --git a/.changeset/eight-bears-draw.md b/.changeset/eight-bears-draw.md new file mode 100644 index 00000000..8c602204 --- /dev/null +++ b/.changeset/eight-bears-draw.md @@ -0,0 +1,5 @@ +--- +"@open-pioneer/test-utils": minor +--- + +Provide `createIntl` from `@open-pioneer/test-utils/vanilla` to make creation of a `PackageIntl` instance easier. diff --git a/src/packages/test-utils/README.md b/src/packages/test-utils/README.md index e353ead7..2df17127 100644 --- a/src/packages/test-utils/README.md +++ b/src/packages/test-utils/README.md @@ -1,4 +1,4 @@ -# @open-pioneer/test-utils +watch# @open-pioneer/test-utils This package contains test utilities that make it easier to test parts of a pioneer application. @@ -112,6 +112,20 @@ it("creates a new service instance with the defined references", async () => { }); ``` +## Utilities for Vanilla JS + +The function `createIntl` from `@open-pioneer/test-utils/vanilla` can be used to make the creation of an `intl` object easier from vanilla JavaScript: + +```js +import { createIntl } from "@open-pioneer/test-utils/vanilla"; + +// In your test: +const intl = createIntl(/* optional arguments such as messages or locale */); +intl.formatMessage({ + /* ... */ +}); +``` + ## License Apache-2.0 (see `LICENSE` file) diff --git a/src/packages/test-utils/build.config.mjs b/src/packages/test-utils/build.config.mjs index 1739a7b3..ed63a1f0 100644 --- a/src/packages/test-utils/build.config.mjs +++ b/src/packages/test-utils/build.config.mjs @@ -3,5 +3,5 @@ import { defineBuildConfig } from "@open-pioneer/build-support"; export default defineBuildConfig({ - entryPoints: ["web-components", "react", "services"] + entryPoints: ["vanilla", "web-components", "react", "services"] }); diff --git a/src/packages/test-utils/react.tsx b/src/packages/test-utils/react.tsx index ef55432d..2e4d2012 100644 --- a/src/packages/test-utils/react.tsx +++ b/src/packages/test-utils/react.tsx @@ -1,14 +1,13 @@ // SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) // SPDX-License-Identifier: Apache-2.0 -import { createIntl, createIntlCache, IntlShape } from "@formatjs/intl"; import { CustomChakraProvider } from "@open-pioneer/chakra-integration"; -import type { Service } from "@open-pioneer/runtime"; +import type { PackageIntl, Service } from "@open-pioneer/runtime"; import { PackageContext as InternalPackageContext, PackageContextMethods } from "@open-pioneer/runtime-react-support"; import { FC, ReactNode, useMemo } from "react"; -import { INTL_ERROR_HANDLER } from "./utils"; +import { createIntl } from "./vanilla"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyService = Service; @@ -80,7 +79,7 @@ function createPackageContextMethods( const properties = options?.properties ?? {}; const locale = options?.locale ?? "en"; const messages = options?.messages ?? {}; - const cachedIntl: Record = {}; + const cachedIntl: Record = {}; return { getService(packageName, interfaceName, options) { if (!options.qualifier) { @@ -127,15 +126,10 @@ function createPackageContextMethods( getIntl(packageName) { const initIntl = () => { const packageMessages = messages[packageName]; - const cache = createIntlCache(); - return createIntl( - { - locale, - messages: packageMessages, - onError: INTL_ERROR_HANDLER - }, - cache - ); + return createIntl({ + locale, + messages: packageMessages + }); }; return (cachedIntl[packageName] ??= initIntl()); } diff --git a/src/packages/test-utils/services.ts b/src/packages/test-utils/services.ts index ec377999..4645411c 100644 --- a/src/packages/test-utils/services.ts +++ b/src/packages/test-utils/services.ts @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) // SPDX-License-Identifier: Apache-2.0 import { ReferenceMeta, Service, ServiceConstructor } from "@open-pioneer/runtime"; -import { createIntl, createIntlCache } from "@formatjs/intl"; -import { INTL_ERROR_HANDLER } from "./utils"; +import { createIntl } from "./vanilla"; /** * Options for the {@link createService} function. @@ -83,20 +82,7 @@ export async function createService ]) ); - const locale = options?.locale ?? "en"; - const defaultMessageLocale = options?.defaultMessageLocale ?? "en"; - const messages = options?.messages ?? {}; - const cache = createIntlCache(); - const intl = createIntl( - { - locale, - defaultLocale: defaultMessageLocale, - messages, - onError: INTL_ERROR_HANDLER - }, - cache - ); - + const intl = createIntl(options); return new clazz({ // eslint-disable-next-line @typescript-eslint/no-explicit-any references: references as any, diff --git a/src/packages/test-utils/utils.ts b/src/packages/test-utils/utils.ts deleted file mode 100644 index c177d42c..00000000 --- a/src/packages/test-utils/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) -// SPDX-License-Identifier: Apache-2.0 -import { IntlErrorCode, OnErrorFn } from "@formatjs/intl"; - -/** Hides missing translation errors during tests */ -export const INTL_ERROR_HANDLER: OnErrorFn = (err) => { - if (err.code === IntlErrorCode.MISSING_TRANSLATION) { - return; - } - - console.error(err); -}; diff --git a/src/packages/test-utils/vanilla.test.ts b/src/packages/test-utils/vanilla.test.ts new file mode 100644 index 00000000..71e64abb --- /dev/null +++ b/src/packages/test-utils/vanilla.test.ts @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { expect, it, afterEach, vi } from "vitest"; +import { createIntl } from "./vanilla"; + +afterEach(() => { + vi.restoreAllMocks(); +}); + +it("creates an intl object", () => { + const intl = createIntl(); + expect(intl.locale).toBe("en"); +}); + +it("creates an intl object with custom locale", () => { + const intl = createIntl({ + locale: "de" + }); + expect(intl.locale).toBe("de"); +}); + +it("creates an intl object with custom messages", () => { + const intl = createIntl({ + messages: { + "foo.bar": "baz" + } + }); + const message = intl.formatMessage({ id: "foo.bar" }); + expect(message).toBe("baz"); +}); + +it("renders the message id and does not warn if a message is not defined", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => undefined); + + const intl = createIntl({ + messages: {} + }); + const message = intl.formatMessage({ id: "foo.bar" }); + expect(message).toBe("foo.bar"); + expect(spy).not.toHaveBeenCalled(); +}); diff --git a/src/packages/test-utils/vanilla.ts b/src/packages/test-utils/vanilla.ts new file mode 100644 index 00000000..7b65a92c --- /dev/null +++ b/src/packages/test-utils/vanilla.ts @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { + createIntlCache, + createIntl as createFormatJsIntl, + IntlErrorCode, + OnErrorFn +} from "@formatjs/intl"; +import { PackageIntl } from "@open-pioneer/runtime"; + +/** Options for `createIntl`. */ +export interface I18nOptions { + /** + * The locale for i18n messages and formatting. + * + * @default "en" + */ + locale?: string; + + /** + * The locale for embedded default messages (e.g. `defaultMessage` property in `intl.formatMessage(...)`). + * It is usually not necessary to specify this option. + * + * See also https://formatjs.io/docs/intl#message-descriptor + * + * @default "en" + */ + defaultMessageLocale?: string; + + /** + * I18n messages as (messageId, message) entries. + * + * @default {} + */ + messages?: Record; +} + +/** + * Creates an `intl` instance that can be used for testing. + * + * Other than the default implementation provided by `@formatjs/intl`, this + * `intl` object will not warn if a message is not defined. + * Instead, it will simply render the fallback message. + * This behavior makes testing easier. + * + * Note that messages can still be defined by using the `messages` parameter. + */ +export function createIntl(options?: I18nOptions): PackageIntl { + const messages = options?.messages ?? {}; + const locale = options?.locale ?? "en"; + const defaultMessageLocale = options?.defaultMessageLocale ?? "en"; + const cache = createIntlCache(); + const intl = createFormatJsIntl( + { + locale, + defaultLocale: defaultMessageLocale, + messages, + onError: INTL_ERROR_HANDLER + }, + cache + ); + return intl; +} + +/** Hides missing translation errors during tests */ +const INTL_ERROR_HANDLER: OnErrorFn = (err) => { + if (err.code === IntlErrorCode.MISSING_TRANSLATION) { + return; + } + + console.error(err); +};