@@ -332,6 +332,7 @@ export class AppManager implements BackstageApp {
|
||||
routeParents={routing.parents}
|
||||
routeObjects={routing.objects}
|
||||
routeBindings={routeBindings}
|
||||
basePath={getBasePath(loadedConfig.api)}
|
||||
>
|
||||
<InternalAppContext.Provider
|
||||
value={{ routeObjects: routing.objects }}
|
||||
@@ -416,7 +417,7 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
{children}
|
||||
</RouterComponent>
|
||||
@@ -437,7 +438,7 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper component={SignInPageComponent}>
|
||||
<>{children}</>
|
||||
|
||||
@@ -60,10 +60,6 @@ export type ErrorBoundaryFallbackProps = {
|
||||
resetError: () => void;
|
||||
};
|
||||
|
||||
export type RouterProps = {
|
||||
basename: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of replaceable core components that are part of every Backstage app.
|
||||
*
|
||||
|
||||
@@ -214,13 +214,15 @@ export class RouteResolver {
|
||||
// Next we figure out the base path, which is the combination of the common parent path
|
||||
// between our current location and our target location, as well as the additional path
|
||||
// that is the difference between the parent path and the base of our target location.
|
||||
const basePath = resolveBasePath(
|
||||
targetRef,
|
||||
relativeSourceLocation,
|
||||
this.routePaths,
|
||||
this.routeParents,
|
||||
this.routeObjects,
|
||||
);
|
||||
const basePath =
|
||||
this.appBasePath +
|
||||
resolveBasePath(
|
||||
targetRef,
|
||||
relativeSourceLocation,
|
||||
this.routePaths,
|
||||
this.routeParents,
|
||||
this.routeObjects,
|
||||
);
|
||||
|
||||
const routeFunc: RouteFunc<Params> = (...[params]) => {
|
||||
return joinPaths(basePath, generatePath(targetPath, params));
|
||||
|
||||
@@ -21,9 +21,11 @@ import {
|
||||
TestApiProvider,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
import { isExternalUri, Link } from './Link';
|
||||
import { analyticsApiRef, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { isExternalUri, Link, useResolvedPath } from './Link';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { renderHook, WrapperComponent } from '@testing-library/react-hooks';
|
||||
|
||||
describe('<Link />', () => {
|
||||
it('navigates using react-router', async () => {
|
||||
@@ -105,6 +107,58 @@ describe('<Link />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves a sub-path correctly', () => {
|
||||
it('when it starts with base path', async () => {
|
||||
const testString = 'This is test string';
|
||||
const linkText = 'Navigate!';
|
||||
const configApi = new ConfigReader({
|
||||
app: { baseUrl: 'http://localhost:3000/example' },
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[configApiRef, configApi]]}>
|
||||
<Link to="/example/test">{linkText}</Link>
|
||||
<Routes>
|
||||
<Route path="/example/test" element={<p>{testString}</p>} />
|
||||
</Routes>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => getByText(testString)).toThrow();
|
||||
fireEvent.click(getByText(linkText));
|
||||
await waitFor(() => {
|
||||
expect(getByText(testString)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('when it does not start with base path', async () => {
|
||||
const testString = 'This is test string';
|
||||
const linkText = 'Navigate!';
|
||||
const configApi = new ConfigReader({
|
||||
app: { baseUrl: 'http://localhost:3000/example' },
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[configApiRef, configApi]]}>
|
||||
<Link to="/test">{linkText}</Link>
|
||||
<Routes>
|
||||
<Route path="/example/test" element={<p>{testString}</p>} />
|
||||
</Routes>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => getByText(testString)).toThrow();
|
||||
fireEvent.click(getByText(linkText));
|
||||
await waitFor(() => {
|
||||
expect(getByText(testString)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isExternalUri', () => {
|
||||
it.each([
|
||||
[true, 'http://'],
|
||||
@@ -130,4 +184,45 @@ describe('<Link />', () => {
|
||||
expect(isExternalUri(uri)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useResolvedPath', () => {
|
||||
const wrapper: WrapperComponent<{}> = ({ children }) => {
|
||||
const configApi = new ConfigReader({
|
||||
app: { baseUrl: 'http://localhost:3000/example' },
|
||||
});
|
||||
return (
|
||||
<TestApiProvider apis={[[configApiRef, configApi]]}>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('concatenate base path', () => {
|
||||
it('when uri is internal and does not start with base path', () => {
|
||||
const path = '/catalog/default/component/artist-lookup';
|
||||
const { result } = renderHook(() => useResolvedPath(path), {
|
||||
wrapper,
|
||||
});
|
||||
expect(result.current).toBe('/example'.concat(path));
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not concatenate base path', () => {
|
||||
it('when uri is external', () => {
|
||||
const path = 'https://stackoverflow.com/questions/1/example';
|
||||
const { result } = renderHook(() => useResolvedPath(path), {
|
||||
wrapper,
|
||||
});
|
||||
expect(result.current).toBe(path);
|
||||
});
|
||||
|
||||
it('when uri already starts with base path', () => {
|
||||
const path = '/example/catalog/default/component/artist-lookup';
|
||||
const { result } = renderHook(() => useResolvedPath(path), {
|
||||
wrapper,
|
||||
});
|
||||
expect(result.current).toBe(path);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
|
||||
import classnames from 'classnames';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import MaterialLink, {
|
||||
|
||||
Reference in New Issue
Block a user