Merge pull request #7201 from thefrontside/mk/theme-provider

Provide API to overload AppThemeProvider
This commit is contained in:
Fredrik Adelöw
2021-09-16 13:35:30 +02:00
committed by GitHub
7 changed files with 49 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': patch
---
Allow users to specify their own AppThemeProvider
+3 -2
View File
@@ -165,6 +165,7 @@ export type AppComponents = {
Progress: ComponentType<{}>;
Router: ComponentType<{}>;
ErrorBoundaryFallback: ComponentType<ErrorBoundaryFallbackProps>;
ThemeProvider: ComponentType<{}>;
SignInPage?: ComponentType<SignInPageProps>;
};
@@ -640,6 +641,6 @@ export class WebStorage implements StorageApi {
// Warnings were encountered during analysis:
//
// src/apis/system/ApiProvider.d.ts:9:5 - (ae-forgotten-export) The symbol "ApiProviderProps" needs to be exported by the entry point index.d.ts
// src/app/types.d.ts:88:5 - (ae-forgotten-export) The symbol "UnknownPluginOutput" needs to be exported by the entry point index.d.ts
// src/app/types.d.ts:99:5 - (ae-forgotten-export) The symbol "AppIcons" needs to be exported by the entry point index.d.ts
// src/app/types.d.ts:89:5 - (ae-forgotten-export) The symbol "UnknownPluginOutput" needs to be exported by the entry point index.d.ts
// src/app/types.d.ts:100:5 - (ae-forgotten-export) The symbol "AppIcons" needs to be exported by the entry point index.d.ts
```
@@ -33,6 +33,7 @@ import {
createRoutableExtension,
} from '@backstage/core-plugin-api';
import { generateBoundRoutes, PrivateAppImpl } from './App';
import { AppThemeProvider } from './AppThemeProvider';
describe('generateBoundRoutes', () => {
it('runs happy path', () => {
@@ -160,6 +161,7 @@ describe('Integration Test', () => {
Progress: () => null,
Router: BrowserRouter,
ErrorBoundaryFallback: () => null,
ThemeProvider: AppThemeProvider,
};
it('runs happy paths', async () => {
+7 -4
View File
@@ -66,7 +66,6 @@ import { RoutingProvider } from '../routing/RoutingProvider';
import { validateRoutes } from '../routing/validation';
import { AppContextProvider } from './AppContext';
import { AppIdentity } from './AppIdentity';
import { AppThemeProvider } from './AppThemeProvider';
import {
AppComponents,
AppConfigLoader,
@@ -151,12 +150,14 @@ function useConfigLoader(
noConfigNode = <BootErrorPage step="load-config" error={config.error} />;
}
const { ThemeProvider } = components;
// Before the config is loaded we can't use a router, so exit early
if (noConfigNode) {
return {
node: (
<ApiProvider apis={ApiRegistry.from([[appThemeApiRef, appThemeApi]])}>
<AppThemeProvider>{noConfigNode}</AppThemeProvider>
<ThemeProvider>{noConfigNode}</ThemeProvider>
</ApiProvider>
),
};
@@ -303,10 +304,12 @@ export class PrivateAppImpl implements BackstageApp {
return loadedConfig.node;
}
const { ThemeProvider } = this.components;
return (
<ApiProvider apis={this.getApiHolder()}>
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<ThemeProvider>
<RoutingProvider
routePaths={routePaths}
routeParents={routeParents}
@@ -316,7 +319,7 @@ export class PrivateAppImpl implements BackstageApp {
>
{children}
</RoutingProvider>
</AppThemeProvider>
</ThemeProvider>
</AppContextProvider>
</ApiProvider>
);
@@ -14,10 +14,15 @@
* limitations under the License.
*/
import { render } from '@testing-library/react';
import React from 'react';
import { render, screen } from '@testing-library/react';
import { renderWithEffects } from '@backstage/test-utils';
import React, { PropsWithChildren } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { defaultConfigLoader, OptionallyWrapInRouter } from './createApp';
import {
defaultConfigLoader,
OptionallyWrapInRouter,
createApp,
} from './createApp';
(process as any).env = { NODE_ENV: 'test' };
const anyEnv = process.env as any;
@@ -115,3 +120,24 @@ describe('OptionallyWrapInRouter', () => {
expect(getByText('Test')).toBeInTheDocument();
});
});
describe('Optional ThemeProvider', () => {
it('should render app with user-provided ThemeProvider', async () => {
const components = {
NotFoundErrorPage: () => null,
BootErrorPage: () => null,
Progress: () => null,
Router: MemoryRouter,
ErrorBoundaryFallback: () => null,
ThemeProvider: ({ children }: PropsWithChildren<{}>) => (
<main role="main">{children}</main>
),
};
const App = createApp({ components }).getProvider();
await renderWithEffects(<App />);
expect(screen.getByRole('main')).toBeInTheDocument();
});
});
@@ -27,6 +27,7 @@ import {
useInRouterContext,
} from 'react-router-dom';
import { PrivateAppImpl } from './App';
import { AppThemeProvider } from './AppThemeProvider';
import { defaultApis } from './defaultApis';
import { defaultAppIcons } from './icons';
import {
@@ -149,6 +150,7 @@ export function createApp(options?: AppOptions) {
Progress: Progress,
Router: BrowserRouter,
ErrorBoundaryFallback: DefaultErrorBoundaryFallback,
ThemeProvider: AppThemeProvider,
...options?.components,
};
const themes = options?.themes ?? [
+1
View File
@@ -72,6 +72,7 @@ export type AppComponents = {
Progress: ComponentType<{}>;
Router: ComponentType<{}>;
ErrorBoundaryFallback: ComponentType<ErrorBoundaryFallbackProps>;
ThemeProvider: ComponentType<{}>;
/**
* An optional sign-in page that will be rendered instead of the AppRouter at startup.