feat: create app mode provider

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2024-03-20 13:42:24 +01:00
committed by Patrik Oldsberg
parent 60167a3980
commit 641a068514
5 changed files with 211 additions and 3 deletions
+2 -1
View File
@@ -142,7 +142,8 @@
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
"cross-env": "^7.0.0"
"cross-env": "^7.0.0",
"msw": "^1.0.0"
},
"bundled": true
}
+5 -2
View File
@@ -108,6 +108,7 @@ import { DevToolsPage } from '@backstage/plugin-devtools';
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unprocessed-entities';
import { NotificationsPage } from '@backstage/plugin-notifications';
import { AppMode } from './AppMode';
const app = createApp({
apis,
@@ -282,8 +283,10 @@ export default app.createRoot(
<AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog />
<AppRouter>
<VisitListener />
<Root>{routes}</Root>
<AppMode>
<VisitListener />
<Root>{routes}</Root>
</AppMode>
</AppRouter>
</>,
);
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import {
MockConfigApi,
TestApiProvider,
renderInTestApp,
setupRequestMockHandlers,
} from '@backstage/test-utils';
import { AppMode } from './AppMode';
import {
configApiRef,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
describe('AppMode', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
const configApiMock = new MockConfigApi({
backend: {
baseUrl: 'http://localhost:7000',
},
});
const fetchApiMock = {
fetch: jest.fn(),
};
const discoveryApiMock = {
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000/app'),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should render the progress component while loading', async () => {
fetchApiMock.fetch.mockReturnValueOnce(new Promise(() => {}));
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[fetchApiRef, fetchApiMock],
]}
>
<AppMode>Test content</AppMode>
</TestApiProvider>,
);
expect(screen.getByTestId('progress')).toBeVisible();
expect(screen.queryByText('Test Content')).not.toBeInTheDocument();
});
it('should render the children even when there is an error', async () => {
const error = new Error('Failed to fetch');
fetchApiMock.fetch.mockRejectedValueOnce(error);
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[fetchApiRef, fetchApiMock],
]}
>
<AppMode>Test content</AppMode>
</TestApiProvider>,
);
await waitFor(() =>
expect(screen.getByText('Test content')).toBeInTheDocument(),
);
expect(screen.queryByTestId('progress')).not.toBeInTheDocument();
expect(screen.queryByText('Failed to fetch')).not.toBeInTheDocument();
});
it('should render the children even when the public index is not available', async () => {
fetchApiMock.fetch.mockResolvedValueOnce({ ok: false });
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[fetchApiRef, fetchApiMock],
]}
>
<AppMode>Test content</AppMode>
</TestApiProvider>,
);
await waitFor(() =>
expect(screen.getByText('Test content')).toBeInTheDocument(),
);
expect(screen.queryByTestId('progress')).not.toBeInTheDocument();
});
it('should render the children also when the public index is available', async () => {
fetchApiMock.fetch.mockResolvedValueOnce({ ok: true });
await renderInTestApp(<AppMode>Test content</AppMode>);
await waitFor(() =>
expect(screen.getByText('Test content')).toBeInTheDocument(),
);
expect(screen.queryByTestId('progress')).not.toBeInTheDocument();
});
it('should render the children wrapped in the CookieAuthRefreshProvider', async () => {
worker.use(
rest.get('http://localhost:7000/public/index.html', (_, res, ctx) => {
return res(ctx.status(200));
}),
rest.get(
'http://localhost:7000/app/.backstage/v1-cookie',
(_, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ expiresAt: Date.now() + 10 * 60 * 1000 }),
);
},
),
);
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[discoveryApiRef, discoveryApiMock],
]}
>
<AppMode>Test content</AppMode>
</TestApiProvider>,
);
await waitFor(() =>
expect(screen.getByText('Test content')).toBeInTheDocument(),
);
});
});
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { ReactNode } from 'react';
import useAsync from 'react-use/lib/useAsync';
import {
configApiRef,
fetchApiRef,
useApi,
useApp,
} from '@backstage/core-plugin-api';
import { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react';
export function AppMode(props: { children: ReactNode }): JSX.Element {
const { children } = props;
const fetchApi = useApi(fetchApiRef);
const configApi = useApi(configApiRef);
const Components = useApp().getComponents();
const { loading, error, value } = useAsync(async () => {
const baseUrl = configApi.getString('backend.baseUrl');
const response = await fetchApi.fetch(`${baseUrl}/public/index.html`);
return response.ok;
}, [fetchApi, configApi]);
if (loading) {
return <Components.Progress />;
}
// Request failed, or the public index is not available
if (error || !value) {
return <>{children}</>;
}
// The public index is available
// That means the app is running in public experimental mode
return (
<CookieAuthRefreshProvider pluginId="app">
{children}
</CookieAuthRefreshProvider>
);
}
+1
View File
@@ -27526,6 +27526,7 @@ __metadata:
"@vitejs/plugin-react": ^4.0.4
cross-env: ^7.0.0
history: ^5.0.0
msw: ^1.0.0
react: ^18.0.2
react-dom: ^18.0.2
react-router: ^6.3.0