Skip to content

OCPBUGS-54670: Use PF component group for PageHeading #14965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions frontend/__tests__/components/container.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
Firehose,
HorizontalNav,
LoadingBox,
PageHeading,
PageHeadingProps,
ConnectedPageHeading,
ConnectedPageHeadingProps,
} from '@console/internal/components/utils';
import { testPodInstance } from '../../__mocks__/k8sResourcesMocks';
import { Status } from '@console/shared';
Expand Down Expand Up @@ -53,13 +53,13 @@ describe(ContainersDetailsPage.displayName, () => {
describe(ContainerDetails.displayName, () => {
const obj = { data: { ...testPodInstance } };

it('renders a `PageHeading` and a `ContainerDetails` with the same state', async () => {
it('renders a `ConnectedPageHeading` and a `ContainerDetails` with the same state', async () => {
jest
.spyOn(ReactRouter, 'useParams')
.mockReturnValue({ podName: 'test-name', ns: 'default', name: 'crash-app' });

jest.spyOn(ReactRouter, 'useLocation').mockReturnValue({ pathname: '' });
// Full mount needed to get the children of the PageHeading within the ContainerDetails without warning
// Full mount needed to get the children of the ConnectedPageHeading within the ContainerDetails without warning
let containerDetails: ReactWrapper;
await act(async () => {
containerDetails = mount(<ContainerDetails obj={obj} loaded={true} />, {
Expand All @@ -72,7 +72,7 @@ describe(ContainerDetails.displayName, () => {
});

const pageHeadingStatusProps = containerDetails
.find<PageHeadingProps>(PageHeading)
.find<ConnectedPageHeadingProps>(ConnectedPageHeading)
.children()
.find<StatusProps>(Status)
.props();
Expand Down
69 changes: 41 additions & 28 deletions frontend/__tests__/components/factory/list-page.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import * as React from 'react';
import { shallow, mount, ShallowWrapper, ReactWrapper } from 'enzyme';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { TextInput } from '@patternfly/react-core';
import {
TextFilter,
ListPageWrapper,
FireMan,
MultiListPage,
} from '../../../public/components/factory/list-page';
import { Firehose, PageHeading } from '../../../public/components/utils';
import { Firehose } from '../../../public/components/utils';

jest.mock('react-redux', () => {
const ActualReactRedux = jest.requireActual('react-redux');
Expand Down Expand Up @@ -74,40 +78,49 @@ describe(TextFilter.displayName, () => {
});

describe(FireMan.displayName, () => {
let wrapper: ShallowWrapper<any>;

beforeEach(() => {
const resources = [{ kind: 'Node', prop: 'obj' }];
wrapper = shallow(<FireMan resources={resources} />);
const mockStore = configureMockStore();
const store = mockStore({
k8s: {
getIn: jest.fn().mockReturnValue({}),
},
sdkCore: {
impersonate: {},
},
});

it('renders `title` if given `title`', () => {
expect(wrapper.find(PageHeading).props().title).toBe(undefined);

const title = 'My pods';
wrapper.setProps({ title });
const { rerender } = render(
<Provider store={store}>
<FireMan resources={[{ kind: 'Node', prop: 'obj' }]} />
</Provider>,
);
expect(screen.queryByText('My pods')).not.toBeInTheDocument();

expect(wrapper.find(PageHeading).props().title).toEqual(title);
rerender(
<Provider store={store}>
<FireMan resources={[{ kind: 'Node', prop: 'obj' }]} title="My pods" />
</Provider>,
);
expect(screen.getByText('My pods')).toBeInTheDocument();
});

it('renders create button if given `canCreate` true', () => {
expect(wrapper.find('button#yaml-create').exists()).toBe(false);

const createProps = { foo: 'bar' };
const button = wrapper
.setProps({
canCreate: true,
createProps,
createButtonText: 'Create Me!',
title: 'Nights Watch',
})
.find('#yaml-create');

expect(wrapper.find('#yaml-create').childAt(0).text()).toEqual('Create Me!');

Object.keys(createProps).forEach((key) => {
expect(createProps[key] === button.props()[key]).toBe(true);
});
const createProps = {};
render(
<Provider store={store}>
<FireMan
resources={[{ kind: 'Node', prop: 'obj' }]}
canCreate
createProps={createProps}
createButtonText="Create Me!"
title="Nights Watch"
/>
</Provider>,
);

const button = screen.getByRole('button', { name: 'Create Me!' });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('id', 'yaml-create');
});
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/__tests__/components/storage-class-form.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ConnectedStorageClassForm,
StorageClassFormProps,
} from '../../public/components/storage-class-form';
import { PageHeading } from '../../public/components/utils';
import { PageHeading } from '@console/shared/src/components/heading/PageHeading';

jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,10 @@
import { configure, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import {
PageHeading,
BreadCrumbs,
BreadCrumbsProps,
} from '../../../public/components/utils/headings';
import { ConnectedPageHeading } from '../../../public/components/utils/headings';
import { testResourceInstance } from '../../../__mocks__/k8sResourcesMocks';
import { MemoryRouter } from 'react-router-dom-v5-compat';

// Mock getRootNode
Object.defineProperty(Element.prototype, 'getRootNode', {
value: function () {
let rootNode = this;
while (rootNode.parentNode) {
rootNode = rootNode.parentNode;
}
return rootNode;
},
configurable: true,
});

describe(BreadCrumbs.displayName, () => {
let breadcrumbs: BreadCrumbsProps['breadcrumbs'];

beforeEach(() => {
configure({ testIdAttribute: 'data-test' });

breadcrumbs = [
{ name: 'pods', path: '/pods' },
{ name: 'containers', path: '/pods/containers' },
];
});

it('renders each given breadcrumb', () => {
render(
<MemoryRouter>
<BreadCrumbs breadcrumbs={breadcrumbs} />
</MemoryRouter>,
);

breadcrumbs.forEach((crumb) => {
if (crumb.path) {
const link = screen.getByRole('link', { name: crumb.name });
expect(link).toHaveAttribute('href', crumb.path);
} else {
expect(screen.getByText(crumb.name)).toBeInTheDocument();
}
});
});
});

describe(PageHeading.displayName, () => {
describe(ConnectedPageHeading.displayName, () => {
beforeEach(() => {
configure({ testIdAttribute: 'data-test' });
});
Expand All @@ -59,7 +13,7 @@ describe(PageHeading.displayName, () => {
const kind = 'Pod';
render(
<MemoryRouter>
<PageHeading.WrappedComponent obj={null} kind={kind} />
<ConnectedPageHeading.WrappedComponent obj={null} kind={kind} />
</MemoryRouter>,
);

Expand All @@ -72,7 +26,7 @@ describe(PageHeading.displayName, () => {
const title = <span>My Custom Title</span>;
render(
<MemoryRouter>
<PageHeading.WrappedComponent obj={null} title={title} />
<ConnectedPageHeading.WrappedComponent obj={null} title={title} />
</MemoryRouter>,
);

Expand All @@ -83,7 +37,7 @@ describe(PageHeading.displayName, () => {
const breadcrumbs = [];
render(
<MemoryRouter>
<PageHeading.WrappedComponent
<ConnectedPageHeading.WrappedComponent
obj={{ data: testResourceInstance, loaded: true, loadError: null }}
breadcrumbsFor={() => breadcrumbs}
/>
Expand All @@ -96,7 +50,7 @@ describe(PageHeading.displayName, () => {
it('does not render breadcrumbs if object has not loaded', () => {
render(
<MemoryRouter>
<PageHeading.WrappedComponent obj={null} breadcrumbsFor={() => []} />
<ConnectedPageHeading.WrappedComponent obj={null} breadcrumbsFor={() => []} />
</MemoryRouter>,
);

Expand Down
64 changes: 42 additions & 22 deletions frontend/before-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,51 @@ import { URLSearchParams } from 'url';
import fetch, { Headers } from 'node-fetch';

// FIXME: Remove when jest is updated to at least 25.1.0 -- see https://github.com/jsdom/jsdom/issues/1555
Element.prototype.closest = function (this, selector) {
// eslint-disable-next-line consistent-this
let el = this;
while (el) {
if (el.matches(selector)) {
return el;
if (!Element.prototype.closest) {
Element.prototype.closest = function (this, selector) {
// eslint-disable-next-line consistent-this
let el = this;
while (el) {
if (el.matches(selector)) {
return el;
}
el = el.parentElement;
}
el = el.parentElement;
}
return null;
};
return null;
};
}
if (!Element.prototype.getRootNode) {
Object.defineProperty(Element.prototype, 'getRootNode', {
value: function () {
let rootNode = this;
while (rootNode.parentNode) {
rootNode = rootNode.parentNode;
}
return rootNode;
},
writable: true,
});
}
// FIXME: Remove when jest is updated to at least 25
Object.defineProperty(window, 'Headers', {
value: Headers,
writable: true,
});
if (!window.Headers) {
Object.defineProperty(window, 'Headers', {
value: Headers,
writable: true,
});
}
// FIXME: Remove when jest is updated to at least 22
Object.defineProperty(window, 'URLSearchParams', {
value: URLSearchParams,
writable: true,
});
Object.defineProperty(window, 'fetch', {
value: fetch,
writable: true,
});
if (!window.URLSearchParams) {
Object.defineProperty(window, 'URLSearchParams', {
value: URLSearchParams,
writable: true,
});
}
if (!window.fetch) {
Object.defineProperty(window, 'fetch', {
value: fetch,
writable: true,
});
}

// http://airbnb.io/enzyme/docs/installation/index.html#working-with-react-16
configure({ adapter: new Adapter() });
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
"@patternfly/react-catalog-view-extension": "^6.1.0",
"@patternfly/react-charts": "^8.2.1",
"@patternfly/react-code-editor": "^6.2.1",
"@patternfly/react-component-groups": "^6.2.0",
"@patternfly/react-component-groups": "^6.2.1",
"@patternfly/react-console": "^6.0.0",
"@patternfly/react-core": "^6.2.1",
"@patternfly/react-icons": "^6.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { IconStatus, Status } from '@patternfly/react-component-groups/dist/dynamic/Status';
import {
Tabs,
Tab,
Expand All @@ -8,14 +9,16 @@ import {
TabContent,
TabContentProps,
TabTitleText,
PageSection,
} from '@patternfly/react-core';
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
import { LockIcon } from '@patternfly/react-icons/dist/esm/icons/lock-icon';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom-v5-compat';
import { LoadingBox, history } from '@console/internal/components/utils';
import { PageLayout, isModifiedEvent } from '@console/shared';
import { isModifiedEvent } from '@console/shared';
import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle';
import PrimaryHeading from '@console/shared/src/components/heading/PrimaryHeading';
import { PageHeading } from '@console/shared/src/components/heading/PageHeading';
import ClusterConfigurationForm from './ClusterConfigurationForm';
import { getClusterConfigurationGroups } from './getClusterConfigurationGroups';
import { ClusterConfigurationTabGroup } from './types';
Expand Down Expand Up @@ -98,12 +101,13 @@ const ClusterConfigurationPage: React.FC = () => {
return (
<div className="co-cluster-configuration-page">
<DocumentTitle>{t('console-app~Cluster configuration')}</DocumentTitle>
<PageLayout
<PageHeading
title={t('console-app~Cluster configuration')}
hint={t(
helpText={t(
'console-app~Set cluster-wide configuration for the console experience. Your changes will be autosaved and will affect after a refresh.',
)}
>
/>
<PageSection>
{!loaded ? (
<LoadingBox />
) : clusterConfigurationTabs.length === 0 ? (
Expand Down Expand Up @@ -138,14 +142,16 @@ const ClusterConfigurationPage: React.FC = () => {
{groupNotFound ? (
/* Similar to a TabContent */
<section className="co-cluster-configuration-page pf-v6-c-tab-content">
<PrimaryHeading>
{t('console-app~{{section}} not found', { section: activeTabId })}
</PrimaryHeading>
<Status
status={IconStatus.warning}
icon={<ExclamationTriangleIcon />}
label={t('console-app~{{section}} not found', { section: activeTabId })}
/>
</section>
) : null}
</>
)}
</PageLayout>
</PageSection>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom-v5-compat';
import { AsyncResourceYAMLEditor } from '@console/internal/components/AsyncResourceYAMLEditor';
import { PageHeading } from '@console/internal/components/utils';
import { MultiNetworkPolicyModel, NetworkPolicyModel } from '@console/internal/models';
import { NetworkPolicyKind } from '@console/internal/module/k8s';
import { PageHeading } from '@console/shared/src/components/heading/PageHeading';
import { SyncedEditor } from '@console/shared/src/components/synced-editor';
import { EditorType } from '@console/shared/src/components/synced-editor/editor-toggle';
import { safeYAMLToJS } from '@console/shared/src/utils/yaml';
Expand Down
Loading