mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
fix: announce navigation to screen readers (#911)
* refactor: unify page titles * refactor: update page title on navigation * refactor: add AnnouncerContext to test contexts * fix: announce navigation to screen readers
This commit is contained in:
parent
06232a5522
commit
f7266cde10
@ -8,7 +8,7 @@
|
|||||||
<meta name="cdnPrefix" content="::cdnPrefix::" />
|
<meta name="cdnPrefix" content="::cdnPrefix::" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content="unleash" />
|
<meta name="description" content="unleash" />
|
||||||
<title>Unleash - Enterprise ready feature toggles</title>
|
<title>Unleash</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
|
||||||
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
|
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
|
import { ANNOUNCER_ELEMENT_TEST_ID } from 'utils/testIds';
|
||||||
|
|
||||||
test('AnnouncerContext', async () => {
|
test('AnnouncerContext', async () => {
|
||||||
const TestComponent = () => {
|
const TestComponent = () => {
|
||||||
@ -16,12 +16,9 @@ test('AnnouncerContext', async () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(<TestComponent />);
|
||||||
<AnnouncerProvider>
|
|
||||||
<TestComponent />
|
|
||||||
</AnnouncerProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByRole('status')).not.toHaveTextContent('Foo');
|
const el = screen.getByTestId(ANNOUNCER_ELEMENT_TEST_ID);
|
||||||
expect(screen.getByRole('status')).toHaveTextContent('Bar');
|
expect(el).not.toHaveTextContent('Foo');
|
||||||
|
expect(el).toHaveTextContent('Bar');
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { useStyles } from 'component/common/Announcer/AnnouncerElement/AnnouncerElement.styles';
|
import { useStyles } from 'component/common/Announcer/AnnouncerElement/AnnouncerElement.styles';
|
||||||
|
import { ANNOUNCER_ELEMENT_TEST_ID } from 'utils/testIds';
|
||||||
|
|
||||||
interface IAnnouncerElementProps {
|
interface IAnnouncerElementProps {
|
||||||
announcement?: string;
|
announcement?: string;
|
||||||
@ -16,6 +17,7 @@ export const AnnouncerElement = ({
|
|||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
aria-atomic
|
aria-atomic
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
|
data-testid={ANNOUNCER_ELEMENT_TEST_ID}
|
||||||
>
|
>
|
||||||
{announcement}
|
{announcement}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { Typography } from '@material-ui/core';
|
|||||||
import ConditionallyRender from '../ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
const HeaderTitle = ({
|
const HeaderTitle = ({
|
||||||
title,
|
title,
|
||||||
@ -18,6 +19,8 @@ const HeaderTitle = ({
|
|||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const headerClasses = classnames({ skeleton: loading });
|
const headerClasses = classnames({ skeleton: loading });
|
||||||
|
|
||||||
|
usePageTitle(title);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.headerTitleContainer}>
|
<div className={styles.headerTitleContainer}>
|
||||||
<div className={headerClasses} data-loading>
|
<div className={headerClasses} data-loading>
|
||||||
|
@ -96,12 +96,12 @@ const FeatureToggleList = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const searchResultsHeader = filter.query
|
const searchResultsHeader = filter.query
|
||||||
? `(${features.length} matches)`
|
? ` (${features.length} matches)`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const headerTitle = archive
|
const headerTitle = archive
|
||||||
? `Archived Features ${searchResultsHeader}`
|
? `Archived feature toggles${searchResultsHeader}`
|
||||||
: `Features ${searchResultsHeader}`;
|
: `Feature toggles${searchResultsHeader}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.featureContainer}>
|
<div className={styles.featureContainer}>
|
||||||
|
@ -6,6 +6,7 @@ import renderer from 'react-test-renderer';
|
|||||||
import theme from 'themes/mainTheme';
|
import theme from 'themes/mainTheme';
|
||||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
|
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
|
||||||
|
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
||||||
|
|
||||||
jest.mock('./FeatureToggleListItem/FeatureToggleListItem', () => ({
|
jest.mock('./FeatureToggleListItem/FeatureToggleListItem', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
@ -24,7 +25,10 @@ test('renders correctly with one feature', () => {
|
|||||||
const tree = renderer.create(
|
const tree = renderer.create(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<AccessProvider permissions={[{ permission: CREATE_FEATURE }]}>
|
<AnnouncerProvider>
|
||||||
|
<AccessProvider
|
||||||
|
permissions={[{ permission: CREATE_FEATURE }]}
|
||||||
|
>
|
||||||
<FeatureToggleList
|
<FeatureToggleList
|
||||||
updateSetting={jest.fn()}
|
updateSetting={jest.fn()}
|
||||||
filter={{}}
|
filter={{}}
|
||||||
@ -36,6 +40,7 @@ test('renders correctly with one feature', () => {
|
|||||||
flags={{}}
|
flags={{}}
|
||||||
/>
|
/>
|
||||||
</AccessProvider>
|
</AccessProvider>
|
||||||
|
</AnnouncerProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
@ -52,7 +57,10 @@ test('renders correctly with one feature without permissions', () => {
|
|||||||
const tree = renderer.create(
|
const tree = renderer.create(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<AccessProvider permissions={[{ permission: CREATE_FEATURE }]}>
|
<AnnouncerProvider>
|
||||||
|
<AccessProvider
|
||||||
|
permissions={[{ permission: CREATE_FEATURE }]}
|
||||||
|
>
|
||||||
<FeatureToggleList
|
<FeatureToggleList
|
||||||
filter={{}}
|
filter={{}}
|
||||||
setFilter={jest.fn()}
|
setFilter={jest.fn()}
|
||||||
@ -63,6 +71,7 @@ test('renders correctly with one feature without permissions', () => {
|
|||||||
flags={{}}
|
flags={{}}
|
||||||
/>
|
/>
|
||||||
</AccessProvider>
|
</AccessProvider>
|
||||||
|
</AnnouncerProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders correctly with one feature 1`] = `
|
exports[`renders correctly with one feature 1`] = `
|
||||||
<div>
|
Array [
|
||||||
|
<div>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-searchBarContainer-3"
|
className="makeStyles-searchBarContainer-3"
|
||||||
>
|
>
|
||||||
@ -70,7 +71,7 @@ exports[`renders correctly with one feature 1`] = `
|
|||||||
<h1
|
<h1
|
||||||
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
|
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
|
||||||
>
|
>
|
||||||
Features
|
Feature toggles
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -169,11 +170,20 @@ exports[`renders correctly with one feature 1`] = `
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
<div
|
||||||
|
aria-atomic={true}
|
||||||
|
aria-live="polite"
|
||||||
|
className="makeStyles-container-18"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
/>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders correctly with one feature without permissions 1`] = `
|
exports[`renders correctly with one feature without permissions 1`] = `
|
||||||
<div>
|
Array [
|
||||||
|
<div>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-searchBarContainer-3"
|
className="makeStyles-searchBarContainer-3"
|
||||||
>
|
>
|
||||||
@ -242,7 +252,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
|||||||
<h1
|
<h1
|
||||||
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
|
className="MuiTypography-root makeStyles-headerTitle-15 MuiTypography-h1"
|
||||||
>
|
>
|
||||||
Features
|
Feature toggles
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -344,5 +354,15 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
<div
|
||||||
|
aria-atomic={true}
|
||||||
|
aria-live="polite"
|
||||||
|
className="makeStyles-container-18"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
Navigated to Feature toggles
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -16,12 +16,14 @@ import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
|
|||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
import { useStyles } from './FeatureMetrics.styles';
|
import { useStyles } from './FeatureMetrics.styles';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
export const FeatureMetrics = () => {
|
export const FeatureMetrics = () => {
|
||||||
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
||||||
const environments = useFeatureMetricsEnvironments(projectId, featureId);
|
const environments = useFeatureMetricsEnvironments(projectId, featureId);
|
||||||
const applications = useFeatureMetricsApplications(featureId);
|
const applications = useFeatureMetricsApplications(featureId);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
usePageTitle('Metrics');
|
||||||
|
|
||||||
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
|
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
|
||||||
useQueryStringNumberState('hoursBack');
|
useQueryStringNumberState('hoursBack');
|
||||||
|
@ -10,15 +10,16 @@ import {
|
|||||||
formatFeaturePath,
|
formatFeaturePath,
|
||||||
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
const FeatureOverview = () => {
|
const FeatureOverview = () => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
|
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const featurePath = formatFeaturePath(projectId, featureId);
|
const featurePath = formatFeaturePath(projectId, featureId);
|
||||||
const onSidebarClose = () => push(featurePath);
|
const onSidebarClose = () => push(featurePath);
|
||||||
|
usePageTitle(featureId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { useStyles } from './FeatureVariants.styles';
|
import { useStyles } from './FeatureVariants.styles';
|
||||||
import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList';
|
import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
const FeatureVariants = () => {
|
const FeatureVariants = () => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
usePageTitle('Variants');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
@ -8,5 +8,5 @@ export const EventHistory = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EventLog history={events} title="Recent changes" />;
|
return <EventLog history={events} title="Event log" />;
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ export const FeatureEventHistory = ({ toggleName }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventLog history={events} hideName title="Change log" displayInline />
|
<EventLog history={events} hideName title="Event log" displayInline />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,6 +23,13 @@ exports[`renders correctly with empty version 1`] = `
|
|||||||
</small>
|
</small>
|
||||||
<br />
|
<br />
|
||||||
</section>
|
</section>
|
||||||
|
<div
|
||||||
|
aria-atomic="true"
|
||||||
|
aria-live="polite"
|
||||||
|
class="makeStyles-container-2"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
@ -34,7 +41,7 @@ exports[`renders correctly with ui-config 1`] = `
|
|||||||
title="API details"
|
title="API details"
|
||||||
>
|
>
|
||||||
<h2
|
<h2
|
||||||
class="makeStyles-title-2"
|
class="makeStyles-title-3"
|
||||||
>
|
>
|
||||||
Unleash 1.1.0
|
Unleash 1.1.0
|
||||||
|
|
||||||
@ -50,6 +57,13 @@ exports[`renders correctly with ui-config 1`] = `
|
|||||||
</small>
|
</small>
|
||||||
<br />
|
<br />
|
||||||
</section>
|
</section>
|
||||||
|
<div
|
||||||
|
aria-atomic="true"
|
||||||
|
aria-live="polite"
|
||||||
|
class="makeStyles-container-4"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
@ -61,7 +75,7 @@ exports[`renders correctly with versionInfo 1`] = `
|
|||||||
title="API details"
|
title="API details"
|
||||||
>
|
>
|
||||||
<h2
|
<h2
|
||||||
class="makeStyles-title-4"
|
class="makeStyles-title-7"
|
||||||
>
|
>
|
||||||
Unleash 1.2.3
|
Unleash 1.2.3
|
||||||
|
|
||||||
@ -77,6 +91,13 @@ exports[`renders correctly with versionInfo 1`] = `
|
|||||||
1
|
1
|
||||||
</small>
|
</small>
|
||||||
</section>
|
</section>
|
||||||
|
<div
|
||||||
|
aria-atomic="true"
|
||||||
|
aria-live="polite"
|
||||||
|
class="makeStyles-container-8"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
@ -88,7 +109,7 @@ exports[`renders correctly without uiConfig 1`] = `
|
|||||||
title="API details"
|
title="API details"
|
||||||
>
|
>
|
||||||
<h2
|
<h2
|
||||||
class="makeStyles-title-3"
|
class="makeStyles-title-5"
|
||||||
>
|
>
|
||||||
Unleash 1.1.0
|
Unleash 1.1.0
|
||||||
|
|
||||||
@ -97,6 +118,13 @@ exports[`renders correctly without uiConfig 1`] = `
|
|||||||
<small />
|
<small />
|
||||||
<br />
|
<br />
|
||||||
</section>
|
</section>
|
||||||
|
<div
|
||||||
|
aria-atomic="true"
|
||||||
|
aria-live="polite"
|
||||||
|
class="makeStyles-container-6"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
|
@ -46,7 +46,7 @@ Array [
|
|||||||
"menu": Object {},
|
"menu": Object {},
|
||||||
"parent": "/projects",
|
"parent": "/projects",
|
||||||
"path": "/projects/:projectId/features/:featureId/edit",
|
"path": "/projects/:projectId/features/:featureId/edit",
|
||||||
"title": "Edit Feature",
|
"title": "Edit feature",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@ -122,7 +122,7 @@ Array [
|
|||||||
"mobile": true,
|
"mobile": true,
|
||||||
},
|
},
|
||||||
"path": "/features",
|
"path": "/features",
|
||||||
"title": "Feature Toggles",
|
"title": "Feature toggles",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@ -169,7 +169,7 @@ Array [
|
|||||||
"mobile": true,
|
"mobile": true,
|
||||||
},
|
},
|
||||||
"path": "/context",
|
"path": "/context",
|
||||||
"title": "Context Fields",
|
"title": "Context fields",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@ -331,14 +331,14 @@ Array [
|
|||||||
"adminSettings": true,
|
"adminSettings": true,
|
||||||
},
|
},
|
||||||
"path": "/history",
|
"path": "/history",
|
||||||
"title": "Event History",
|
"title": "Event log",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": Object {},
|
"menu": Object {},
|
||||||
"path": "/archive",
|
"path": "/archive",
|
||||||
"title": "Archived Toggles",
|
"title": "Archived toggles",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@ -402,7 +402,7 @@ Array [
|
|||||||
},
|
},
|
||||||
"parent": "/admin",
|
"parent": "/admin",
|
||||||
"path": "/admin/roles",
|
"path": "/admin/roles",
|
||||||
"title": "Project Roles",
|
"title": "Project roles",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@ -412,7 +412,7 @@ Array [
|
|||||||
},
|
},
|
||||||
"parent": "/admin",
|
"parent": "/admin",
|
||||||
"path": "/admin/auth",
|
"path": "/admin/auth",
|
||||||
"title": "Single Sign-On",
|
"title": "Single sign-on",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
@ -98,7 +98,7 @@ export const routes: IRoute[] = [
|
|||||||
{
|
{
|
||||||
path: '/projects/:projectId/features/:featureId/edit',
|
path: '/projects/:projectId/features/:featureId/edit',
|
||||||
parent: '/projects',
|
parent: '/projects',
|
||||||
title: 'Edit Feature',
|
title: 'Edit feature',
|
||||||
component: EditFeature,
|
component: EditFeature,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: {},
|
menu: {},
|
||||||
@ -172,7 +172,7 @@ export const routes: IRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/features',
|
path: '/features',
|
||||||
title: 'Feature Toggles',
|
title: 'Feature toggles',
|
||||||
component: FeatureToggleListContainer,
|
component: FeatureToggleListContainer,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { mobile: true },
|
menu: { mobile: true },
|
||||||
@ -216,7 +216,7 @@ export const routes: IRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/context',
|
path: '/context',
|
||||||
title: 'Context Fields',
|
title: 'Context fields',
|
||||||
component: ContextList,
|
component: ContextList,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
flag: C,
|
flag: C,
|
||||||
@ -372,7 +372,7 @@ export const routes: IRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/history',
|
path: '/history',
|
||||||
title: 'Event History',
|
title: 'Event log',
|
||||||
component: EventHistoryPage,
|
component: EventHistoryPage,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
@ -381,7 +381,7 @@ export const routes: IRoute[] = [
|
|||||||
// Archive
|
// Archive
|
||||||
{
|
{
|
||||||
path: '/archive',
|
path: '/archive',
|
||||||
title: 'Archived Toggles',
|
title: 'Archived toggles',
|
||||||
component: ArchiveListContainer,
|
component: ArchiveListContainer,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: {},
|
menu: {},
|
||||||
@ -447,7 +447,7 @@ export const routes: IRoute[] = [
|
|||||||
{
|
{
|
||||||
path: '/admin/roles',
|
path: '/admin/roles',
|
||||||
parent: '/admin',
|
parent: '/admin',
|
||||||
title: 'Project Roles',
|
title: 'Project roles',
|
||||||
component: ProjectRoles,
|
component: ProjectRoles,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
flag: RE,
|
flag: RE,
|
||||||
@ -456,7 +456,7 @@ export const routes: IRoute[] = [
|
|||||||
{
|
{
|
||||||
path: '/admin/auth',
|
path: '/admin/auth',
|
||||||
parent: '/admin',
|
parent: '/admin',
|
||||||
title: 'Single Sign-On',
|
title: 'Single sign-on',
|
||||||
component: AuthSettings,
|
component: AuthSettings,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
|
@ -49,7 +49,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
headerContent={
|
headerContent={
|
||||||
<HeaderTitle
|
<HeaderTitle
|
||||||
className={styles.title}
|
className={styles.title}
|
||||||
title={`Feature toggles (${filteredFeatures.length})`}
|
title={`Project features (${filteredFeatures.length})`}
|
||||||
actions={
|
actions={
|
||||||
<div className={styles.actionsContainer}>
|
<div className={styles.actionsContainer}>
|
||||||
<SearchField
|
<SearchField
|
||||||
|
@ -3,12 +3,15 @@ import ApiError from 'component/common/ApiError/ApiError';
|
|||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
import ReportToggleList from 'component/Reporting/ReportToggleList/ReportToggleList';
|
import ReportToggleList from 'component/Reporting/ReportToggleList/ReportToggleList';
|
||||||
import { ReportCard } from 'component/Reporting/ReportCard/ReportCard';
|
import { ReportCard } from 'component/Reporting/ReportCard/ReportCard';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
interface IProjectHealthProps {
|
interface IProjectHealthProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectHealth = ({ projectId }: IProjectHealthProps) => {
|
const ProjectHealth = ({ projectId }: IProjectHealthProps) => {
|
||||||
|
usePageTitle('Project health');
|
||||||
|
|
||||||
const { healthReport, refetchHealthReport, error } =
|
const { healthReport, refetchHealthReport, error } =
|
||||||
useHealthReport(projectId);
|
useHealthReport(projectId);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import theme from 'themes/mainTheme';
|
|||||||
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
|
import AccessProvider from 'component/providers/AccessProvider/AccessProvider';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import UIProvider from 'component/providers/UIProvider/UIProvider';
|
import UIProvider from 'component/providers/UIProvider/UIProvider';
|
||||||
|
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
||||||
|
|
||||||
test('renders correctly with one strategy', () => {
|
test('renders correctly with one strategy', () => {
|
||||||
const strategy = {
|
const strategy = {
|
||||||
@ -15,6 +16,7 @@ test('renders correctly with one strategy', () => {
|
|||||||
const tree = renderer.create(
|
const tree = renderer.create(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<AnnouncerProvider>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
<StrategiesList
|
<StrategiesList
|
||||||
@ -26,6 +28,7 @@ test('renders correctly with one strategy', () => {
|
|||||||
/>
|
/>
|
||||||
</AccessProvider>
|
</AccessProvider>
|
||||||
</UIProvider>
|
</UIProvider>
|
||||||
|
</AnnouncerProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
@ -41,6 +44,7 @@ test('renders correctly with one strategy without permissions', () => {
|
|||||||
const tree = renderer.create(
|
const tree = renderer.create(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<AnnouncerProvider>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
<AccessProvider permissions={[{ permission: ADMIN }]}>
|
||||||
<StrategiesList
|
<StrategiesList
|
||||||
@ -52,6 +56,7 @@ test('renders correctly with one strategy without permissions', () => {
|
|||||||
/>
|
/>
|
||||||
</AccessProvider>
|
</AccessProvider>
|
||||||
</UIProvider>
|
</UIProvider>
|
||||||
|
</AnnouncerProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders correctly with one strategy 1`] = `
|
exports[`renders correctly with one strategy 1`] = `
|
||||||
<div
|
Array [
|
||||||
|
<div
|
||||||
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
|
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
@ -9,7 +10,7 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
"boxShadow": "none",
|
"boxShadow": "none",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-headerContainer-2"
|
className="makeStyles-headerContainer-2"
|
||||||
>
|
>
|
||||||
@ -267,11 +268,22 @@ exports[`renders correctly with one strategy 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
<div
|
||||||
|
aria-atomic={true}
|
||||||
|
aria-live="polite"
|
||||||
|
className="makeStyles-container-11"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
Navigated to Strategies
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders correctly with one strategy without permissions 1`] = `
|
exports[`renders correctly with one strategy without permissions 1`] = `
|
||||||
<div
|
Array [
|
||||||
|
<div
|
||||||
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
|
className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
@ -279,7 +291,7 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
"boxShadow": "none",
|
"boxShadow": "none",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="makeStyles-headerContainer-2"
|
className="makeStyles-headerContainer-2"
|
||||||
>
|
>
|
||||||
@ -537,5 +549,15 @@ exports[`renders correctly with one strategy without permissions 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
<div
|
||||||
|
aria-atomic={true}
|
||||||
|
aria-live="polite"
|
||||||
|
className="makeStyles-container-11"
|
||||||
|
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
Navigated to Strategies
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
21
frontend/src/hooks/usePageTitle.ts
Normal file
21
frontend/src/hooks/usePageTitle.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useContext } from 'react';
|
||||||
|
import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext';
|
||||||
|
|
||||||
|
export const usePageTitle = (title: string) => {
|
||||||
|
const { setAnnouncement } = useContext(AnnouncerContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = title;
|
||||||
|
return () => {
|
||||||
|
document.title = DEFAULT_PAGE_TITLE;
|
||||||
|
};
|
||||||
|
}, [title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (title !== DEFAULT_PAGE_TITLE) {
|
||||||
|
setAnnouncement(`Navigated to ${title}`);
|
||||||
|
}
|
||||||
|
}, [setAnnouncement, title]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_TITLE = 'Unleash';
|
@ -49,3 +49,4 @@ export const INPUT_ERROR_TEXT = 'INPUT_ERROR_TEXT';
|
|||||||
export const HEADER_USER_AVATAR = 'HEADER_USER_AVATAR';
|
export const HEADER_USER_AVATAR = 'HEADER_USER_AVATAR';
|
||||||
export const SIDEBAR_MODAL_ID = 'SIDEBAR_MODAL_ID';
|
export const SIDEBAR_MODAL_ID = 'SIDEBAR_MODAL_ID';
|
||||||
export const AUTH_PAGE_ID = 'AUTH_PAGE_ID';
|
export const AUTH_PAGE_ID = 'AUTH_PAGE_ID';
|
||||||
|
export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID';
|
||||||
|
@ -3,6 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
|
|||||||
import { render as rtlRender, RenderOptions } from '@testing-library/react';
|
import { render as rtlRender, RenderOptions } from '@testing-library/react';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
import { MainThemeProvider } from 'themes/MainThemeProvider';
|
import { MainThemeProvider } from 'themes/MainThemeProvider';
|
||||||
|
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
||||||
|
|
||||||
export const render = (
|
export const render = (
|
||||||
ui: JSX.Element,
|
ui: JSX.Element,
|
||||||
@ -23,7 +24,9 @@ const Wrapper: FC = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<SWRConfig value={{ provider: () => new Map() }}>
|
<SWRConfig value={{ provider: () => new Map() }}>
|
||||||
<MainThemeProvider>
|
<MainThemeProvider>
|
||||||
|
<AnnouncerProvider>
|
||||||
<Router>{children}</Router>
|
<Router>{children}</Router>
|
||||||
|
</AnnouncerProvider>
|
||||||
</MainThemeProvider>
|
</MainThemeProvider>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user