core-plugin-api: make useTranslationRef handle and log errors

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-16 12:34:18 +02:00
parent 83fb7c5e16
commit f31de6be7c
2 changed files with 81 additions and 31 deletions
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import { TestApiProvider } from '@backstage/test-utils';
import React, { ReactNode } from 'react';
import { TestApiProvider, withLogCollector } from '@backstage/test-utils';
import { renderHook } from '@testing-library/react-hooks';
import { createTranslationRef } from './TranslationRef';
import { useTranslationRef } from './useTranslationRef';
@@ -23,7 +23,11 @@ import { useTranslationRef } from './useTranslationRef';
import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppLanguageSelector } from '../../..//core-app-api/src/apis/implementations/AppLanguageApi';
import { createTranslationResource, translationApiRef } from '../alpha';
import {
createTranslationResource,
TranslationApi,
translationApiRef,
} from '../alpha';
const plainRef = createTranslationRef({
id: 'plain',
@@ -33,6 +37,15 @@ const plainRef = createTranslationRef({
},
});
function makeWrapper(translationApi: TranslationApi) {
return ({ children }: { children: ReactNode }) => (
<TestApiProvider
apis={[[translationApiRef, translationApi]]}
children={children}
/>
);
}
describe('useTranslationRef', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -43,12 +56,7 @@ describe('useTranslationRef', () => {
const translationApi = I18nextTranslationApi.create({ languageApi });
const { result } = renderHook(() => useTranslationRef(plainRef), {
wrapper: ({ children }) => (
<TestApiProvider
apis={[[translationApiRef, translationApi]]}
children={children}
/>
),
wrapper: makeWrapper(translationApi),
});
const { t } = result.current;
@@ -75,12 +83,7 @@ describe('useTranslationRef', () => {
const { result, waitForNextUpdate } = renderHook(
() => useTranslationRef(plainRef),
{
wrapper: ({ children }) => (
<TestApiProvider
apis={[[translationApiRef, translationApi]]}
children={children}
/>
),
wrapper: makeWrapper(translationApi),
},
);
@@ -112,12 +115,7 @@ describe('useTranslationRef', () => {
const { result, waitForNextUpdate } = renderHook(
() => useTranslationRef(plainRef),
{
wrapper: ({ children }) => (
<TestApiProvider
apis={[[translationApiRef, translationApi]]}
children={children}
/>
),
wrapper: makeWrapper(translationApi),
},
);
@@ -159,12 +157,7 @@ describe('useTranslationRef', () => {
const { result, waitForNextUpdate } = renderHook(
() => useTranslationRef(resourceRef),
{
wrapper: ({ children }) => (
<TestApiProvider
apis={[[translationApiRef, translationApi]]}
children={children}
/>
),
wrapper: makeWrapper(translationApi),
},
);
@@ -175,4 +168,46 @@ describe('useTranslationRef', () => {
expect(t('key1')).toBe('de1');
expect(t('key2')).toBe('de2');
});
it('should log once and then ignore loading errors', async () => {
const ref = createTranslationRef({
id: 'test',
messages: {
key: 'default',
},
});
const languageApi = AppLanguageSelector.create();
const translationApi = I18nextTranslationApi.create({
languageApi,
resources: [
createTranslationResource({
ref,
translations: {
en: async () => {
throw new Error('NOPE');
},
},
}),
],
});
const rendered1 = renderHook(() => useTranslationRef(ref), {
wrapper: makeWrapper(translationApi),
});
const rendered2 = renderHook(() => useTranslationRef(ref), {
wrapper: makeWrapper(translationApi),
});
const { error } = await withLogCollector(['error'], async () => {
await rendered2.waitForNextUpdate();
});
expect(error).toEqual([
"Failed to load translation resource 'test'; caused by Error: NOPE",
]);
expect(rendered1.result.current.t('key')).toBe('default');
expect(rendered2.result.current.t('key')).toBe('default');
});
});
@@ -24,6 +24,9 @@ import {
} from '../apis/alpha';
import { TranslationRef } from './TranslationRef';
// Make sure we don't fill the logs with loading errors for the same ref
const loggedRefs = new WeakSet<TranslationRef<string, {}>>();
/** @alpha */
export const useTranslationRef = <
TMessages extends { [key in string]: string },
@@ -61,11 +64,23 @@ export const useTranslationRef = <
if (!snapshot.ready) {
throw new Promise<void>(resolve => {
const subscription = observable.subscribe(next => {
if (next.ready) {
subscription.unsubscribe();
const subscription = observable.subscribe({
next(next) {
if (next.ready) {
subscription.unsubscribe();
resolve();
}
},
error(err) {
if (!loggedRefs.has(translationRef)) {
// eslint-disable-next-line no-console
console.error(
`Failed to load translation resource '${translationRef.id}'; caused by ${err}`,
);
loggedRefs.add(translationRef);
}
resolve();
}
},
});
});
}