test(catalog): test Tabbed implementation
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { Tabbed } from './Tabbed';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Routes, Route } from 'react-router';
|
||||
|
||||
describe('Tabbed layout', () => {
|
||||
it('renders simplest case', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<Tabbed.Layout>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title"
|
||||
path="*"
|
||||
element={<div>tabbed-test-content</div>}
|
||||
/>
|
||||
</Tabbed.Layout>,
|
||||
);
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
|
||||
expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates when user clicks different tab', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<Routes>
|
||||
<Route
|
||||
path="/*"
|
||||
element={
|
||||
<Tabbed.Layout>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title"
|
||||
path="/"
|
||||
element={<div>tabbed-test-content</div>}
|
||||
/>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title-2"
|
||||
path="/some-other-path"
|
||||
element={<div>tabbed-test-content-2</div>}
|
||||
/>
|
||||
</Tabbed.Layout>
|
||||
}
|
||||
/>
|
||||
</Routes>,
|
||||
);
|
||||
|
||||
const secondTab = rendered.queryAllByRole('tab')[1];
|
||||
act(() => {
|
||||
fireEvent.click(secondTab);
|
||||
});
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('correctly delegates nested links', () => {
|
||||
const renderRoute = (route: string) =>
|
||||
renderInTestApp(
|
||||
<Routes>
|
||||
<Route
|
||||
path="/*"
|
||||
element={
|
||||
<Tabbed.Layout>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title"
|
||||
path="/"
|
||||
element={<div>tabbed-test-content</div>}
|
||||
/>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title-2"
|
||||
path="/some-other-path/*"
|
||||
element={
|
||||
<div>
|
||||
tabbed-test-content-2
|
||||
<Routes>
|
||||
<Route
|
||||
path="/nested"
|
||||
element={<div>tabbed-test-nested-content-2</div>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Tabbed.Layout>
|
||||
}
|
||||
/>
|
||||
</Routes>,
|
||||
{ routeEntries: [route] },
|
||||
);
|
||||
|
||||
it('works for nested content', async () => {
|
||||
const rendered = await renderRoute('/some-other-path/nested');
|
||||
|
||||
expect(
|
||||
rendered.queryByText('tabbed-test-content'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.queryByText('tabbed-test-nested-content-2'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('works for non-nested content', async () => {
|
||||
const rendered = await renderRoute('/some-other-path/');
|
||||
|
||||
expect(
|
||||
rendered.queryByText('tabbed-test-content'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.queryByText('tabbed-test-nested-content-2'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows only one tab contents at a time', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<Tabbed.Layout>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title"
|
||||
path="/"
|
||||
element={<div>tabbed-test-content</div>}
|
||||
/>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title-2"
|
||||
path="/some-other-path"
|
||||
element={<div>tabbed-test-content-2</div>}
|
||||
/>
|
||||
</Tabbed.Layout>,
|
||||
{ routeEntries: ['/some-other-path'] },
|
||||
);
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('redirects to the top level when no route is matching the url', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<Tabbed.Layout>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title"
|
||||
path="/"
|
||||
element={<div>tabbed-test-content</div>}
|
||||
/>
|
||||
<Tabbed.Content
|
||||
title="tabbed-test-title-2"
|
||||
path="/some-other-path"
|
||||
element={<div>tabbed-test-content-2</div>}
|
||||
/>
|
||||
</Tabbed.Layout>,
|
||||
{ routeEntries: ['/non-existing-path'] },
|
||||
);
|
||||
|
||||
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
|
||||
expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
|
||||
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
rendered.queryByText('tabbed-test-content-2'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,23 @@ const getSelectedIndex = (matchedRoute: RouteMatch, tabs: Tab[]) => {
|
||||
return ~tabIndex ? tabIndex : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compound component, which allows you to define layout
|
||||
* for EntityPage using Tabs as a subnavigation mechanism
|
||||
* Constists of 2 parts: Tabbed.Layout and Tabbed.Content.
|
||||
* Takes care of: tabs, routes, document titles, spacing around content
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Tabbed.Layout>
|
||||
* <Tabbed.Content
|
||||
* title="Example tab"
|
||||
* route="/example/*"
|
||||
* element={<div>This is rendered under /example/anything-here route</div>}
|
||||
* />
|
||||
* </TabbedLayout>
|
||||
* ```
|
||||
*/
|
||||
export const Tabbed = {
|
||||
Layout: ({ children }: { children: React.ReactNode }) => {
|
||||
const routes: PartialRouteObject[] = [];
|
||||
@@ -73,7 +90,7 @@ export const Tabbed = {
|
||||
const title = currentTab.label;
|
||||
|
||||
const onTabChange = (index: number) =>
|
||||
navigate(tabs[index].id.slice(1, -2));
|
||||
navigate(tabs[index].id.replace(/\/\*$/g, ''));
|
||||
|
||||
const currentRouteElement = useRoutes(routes);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user