diff --git a/frontend/public/index.html b/frontend/public/index.html index 3206f3640c..f8f78aee64 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -8,7 +8,7 @@ - Unleash - Enterprise ready feature toggles + Unleash { const TestComponent = () => { @@ -16,12 +16,9 @@ test('AnnouncerContext', async () => { return null; }; - render( - - - - ); + render(); - expect(screen.getByRole('status')).not.toHaveTextContent('Foo'); - expect(screen.getByRole('status')).toHaveTextContent('Bar'); + const el = screen.getByTestId(ANNOUNCER_ELEMENT_TEST_ID); + expect(el).not.toHaveTextContent('Foo'); + expect(el).toHaveTextContent('Bar'); }); diff --git a/frontend/src/component/common/Announcer/AnnouncerElement/AnnouncerElement.tsx b/frontend/src/component/common/Announcer/AnnouncerElement/AnnouncerElement.tsx index 5bf7949340..2c2dbe4440 100644 --- a/frontend/src/component/common/Announcer/AnnouncerElement/AnnouncerElement.tsx +++ b/frontend/src/component/common/Announcer/AnnouncerElement/AnnouncerElement.tsx @@ -1,5 +1,6 @@ import React, { ReactElement } from 'react'; import { useStyles } from 'component/common/Announcer/AnnouncerElement/AnnouncerElement.styles'; +import { ANNOUNCER_ELEMENT_TEST_ID } from 'utils/testIds'; interface IAnnouncerElementProps { announcement?: string; @@ -16,6 +17,7 @@ export const AnnouncerElement = ({ aria-live="polite" aria-atomic className={styles.container} + data-testid={ANNOUNCER_ELEMENT_TEST_ID} > {announcement} diff --git a/frontend/src/component/common/HeaderTitle/HeaderTitle.jsx b/frontend/src/component/common/HeaderTitle/HeaderTitle.jsx index 740959fa73..00d0d653f5 100644 --- a/frontend/src/component/common/HeaderTitle/HeaderTitle.jsx +++ b/frontend/src/component/common/HeaderTitle/HeaderTitle.jsx @@ -6,6 +6,7 @@ import { Typography } from '@material-ui/core'; import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; import { useStyles } from './styles'; +import { usePageTitle } from 'hooks/usePageTitle'; const HeaderTitle = ({ title, @@ -18,6 +19,8 @@ const HeaderTitle = ({ const styles = useStyles(); const headerClasses = classnames({ skeleton: loading }); + usePageTitle(title); + return (
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx index 92857f3916..9a41d138fb 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx @@ -96,12 +96,12 @@ const FeatureToggleList = ({ }; const searchResultsHeader = filter.query - ? `(${features.length} matches)` + ? ` (${features.length} matches)` : ''; const headerTitle = archive - ? `Archived Features ${searchResultsHeader}` - : `Features ${searchResultsHeader}`; + ? `Archived feature toggles${searchResultsHeader}` + : `Feature toggles${searchResultsHeader}`; return (
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx index cacb9ec768..d4d6ca90a5 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.test.jsx @@ -6,6 +6,7 @@ import renderer from 'react-test-renderer'; import theme from 'themes/mainTheme'; import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; import AccessProvider from 'component/providers/AccessProvider/AccessProvider'; +import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; jest.mock('./FeatureToggleListItem/FeatureToggleListItem', () => ({ __esModule: true, @@ -24,18 +25,22 @@ test('renders correctly with one feature', () => { const tree = renderer.create( - - - + + + + + ); @@ -52,17 +57,21 @@ test('renders correctly with one feature without permissions', () => { const tree = renderer.create( - - - + + + + + ); diff --git a/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap index 1fb5a3cae3..1bc574e1d5 100644 --- a/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap +++ b/frontend/src/component/feature/FeatureToggleList/__snapshots__/FeatureToggleList.test.jsx.snap @@ -1,348 +1,368 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly with one feature 1`] = ` -
-
-
-
- - - -
- -
-
-
- - Archive - -
-
+Array [ +
-
-

- Features -

+ + +
+ +
+ + + Archive + +
+
+
+

+ Feature toggles +

+
+
-

- Sorted by: -

- + +
-
-
-
    - + -
+ flags={Object {}} + hasAccess={[Function]} + /> + +
-
-
+
, +
, +] `; exports[`renders correctly with one feature without permissions 1`] = ` -
-
-
-
- - - -
- -
-
-
- - Archive - -
-
+Array [ +
-
-

- Features -

+ + +
+ +
+ + + Archive + +
+
+
+

+ Feature toggles +

+
+
-

- Sorted by: -

- + + +
-
-
-
    - + -
+ flags={Object {}} + hasAccess={[Function]} + /> + +
-
-
+
, +
+ Navigated to Feature toggles +
, +] `; diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx index ceec40abfa..0eb3ffca28 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx @@ -16,12 +16,14 @@ import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import ConditionallyRender from 'component/common/ConditionallyRender'; import { useStyles } from './FeatureMetrics.styles'; +import { usePageTitle } from 'hooks/usePageTitle'; export const FeatureMetrics = () => { const { projectId, featureId } = useParams(); const environments = useFeatureMetricsEnvironments(projectId, featureId); const applications = useFeatureMetricsApplications(featureId); const styles = useStyles(); + usePageTitle('Metrics'); const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] = useQueryStringNumberState('hoursBack'); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx index 98067df8b9..13c43bc569 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx @@ -10,15 +10,16 @@ import { formatFeaturePath, } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { usePageTitle } from 'hooks/usePageTitle'; const FeatureOverview = () => { const styles = useStyles(); const { push } = useHistory(); - const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const featurePath = formatFeaturePath(projectId, featureId); const onSidebarClose = () => push(featurePath); + usePageTitle(featureId); return (
diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariants.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariants.tsx index 7b4654645c..97cce2b3a4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariants.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariants.tsx @@ -1,8 +1,10 @@ import { useStyles } from './FeatureVariants.styles'; import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList'; +import { usePageTitle } from 'hooks/usePageTitle'; const FeatureVariants = () => { const styles = useStyles(); + usePageTitle('Variants'); return (
diff --git a/frontend/src/component/history/EventHistory/EventHistory.tsx b/frontend/src/component/history/EventHistory/EventHistory.tsx index 624a9bb627..9370f32030 100644 --- a/frontend/src/component/history/EventHistory/EventHistory.tsx +++ b/frontend/src/component/history/EventHistory/EventHistory.tsx @@ -8,5 +8,5 @@ export const EventHistory = () => { return null; } - return ; + return ; }; diff --git a/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.jsx b/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.jsx index 7ddb32fa53..6841342865 100644 --- a/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.jsx +++ b/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.jsx @@ -10,7 +10,7 @@ export const FeatureEventHistory = ({ toggleName }) => { } return ( - + ); }; diff --git a/frontend/src/component/menu/Footer/ApiDetails/__snapshots__/ApiDetails.test.tsx.snap b/frontend/src/component/menu/Footer/ApiDetails/__snapshots__/ApiDetails.test.tsx.snap index 40650aa7e0..545211aad7 100644 --- a/frontend/src/component/menu/Footer/ApiDetails/__snapshots__/ApiDetails.test.tsx.snap +++ b/frontend/src/component/menu/Footer/ApiDetails/__snapshots__/ApiDetails.test.tsx.snap @@ -23,6 +23,13 @@ exports[`renders correctly with empty version 1`] = `
+
`; @@ -34,7 +41,7 @@ exports[`renders correctly with ui-config 1`] = ` title="API details" >

Unleash 1.1.0 @@ -50,6 +57,13 @@ exports[`renders correctly with ui-config 1`] = `
+
`; @@ -61,7 +75,7 @@ exports[`renders correctly with versionInfo 1`] = ` title="API details" >

Unleash 1.2.3 @@ -77,6 +91,13 @@ exports[`renders correctly with versionInfo 1`] = ` 1 +
`; @@ -88,7 +109,7 @@ exports[`renders correctly without uiConfig 1`] = ` title="API details" >

Unleash 1.1.0 @@ -97,6 +118,13 @@ exports[`renders correctly without uiConfig 1`] = `
+
`; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap index 1d43d6bd5d..89cea78e85 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routesTest.jsx.snap @@ -46,7 +46,7 @@ Array [ "menu": Object {}, "parent": "/projects", "path": "/projects/:projectId/features/:featureId/edit", - "title": "Edit Feature", + "title": "Edit feature", "type": "protected", }, Object { @@ -122,7 +122,7 @@ Array [ "mobile": true, }, "path": "/features", - "title": "Feature Toggles", + "title": "Feature toggles", "type": "protected", }, Object { @@ -169,7 +169,7 @@ Array [ "mobile": true, }, "path": "/context", - "title": "Context Fields", + "title": "Context fields", "type": "protected", }, Object { @@ -331,14 +331,14 @@ Array [ "adminSettings": true, }, "path": "/history", - "title": "Event History", + "title": "Event log", "type": "protected", }, Object { "component": [Function], "menu": Object {}, "path": "/archive", - "title": "Archived Toggles", + "title": "Archived toggles", "type": "protected", }, Object { @@ -402,7 +402,7 @@ Array [ }, "parent": "/admin", "path": "/admin/roles", - "title": "Project Roles", + "title": "Project roles", "type": "protected", }, Object { @@ -412,7 +412,7 @@ Array [ }, "parent": "/admin", "path": "/admin/auth", - "title": "Single Sign-On", + "title": "Single sign-on", "type": "protected", }, Object { diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index da79cd5ecb..f8f060c921 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -98,7 +98,7 @@ export const routes: IRoute[] = [ { path: '/projects/:projectId/features/:featureId/edit', parent: '/projects', - title: 'Edit Feature', + title: 'Edit feature', component: EditFeature, type: 'protected', menu: {}, @@ -172,7 +172,7 @@ export const routes: IRoute[] = [ }, { path: '/features', - title: 'Feature Toggles', + title: 'Feature toggles', component: FeatureToggleListContainer, type: 'protected', menu: { mobile: true }, @@ -216,7 +216,7 @@ export const routes: IRoute[] = [ }, { path: '/context', - title: 'Context Fields', + title: 'Context fields', component: ContextList, type: 'protected', flag: C, @@ -372,7 +372,7 @@ export const routes: IRoute[] = [ }, { path: '/history', - title: 'Event History', + title: 'Event log', component: EventHistoryPage, type: 'protected', menu: { adminSettings: true }, @@ -381,7 +381,7 @@ export const routes: IRoute[] = [ // Archive { path: '/archive', - title: 'Archived Toggles', + title: 'Archived toggles', component: ArchiveListContainer, type: 'protected', menu: {}, @@ -447,7 +447,7 @@ export const routes: IRoute[] = [ { path: '/admin/roles', parent: '/admin', - title: 'Project Roles', + title: 'Project roles', component: ProjectRoles, type: 'protected', flag: RE, @@ -456,7 +456,7 @@ export const routes: IRoute[] = [ { path: '/admin/auth', parent: '/admin', - title: 'Single Sign-On', + title: 'Single sign-on', component: AuthSettings, type: 'protected', menu: { adminSettings: true }, diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 5bc6622865..400495065f 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -49,7 +49,7 @@ export const ProjectFeatureToggles = ({ headerContent={ { + usePageTitle('Project health'); + const { healthReport, refetchHealthReport, error } = useHealthReport(projectId); diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.test.jsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.test.jsx index ffc9389b40..e45eed4363 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.test.jsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.test.jsx @@ -6,6 +6,7 @@ import theme from 'themes/mainTheme'; import AccessProvider from 'component/providers/AccessProvider/AccessProvider'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import UIProvider from 'component/providers/UIProvider/UIProvider'; +import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; test('renders correctly with one strategy', () => { const strategy = { @@ -15,17 +16,19 @@ test('renders correctly with one strategy', () => { const tree = renderer.create( - - - - - + + + + + + + ); @@ -41,17 +44,19 @@ test('renders correctly with one strategy without permissions', () => { const tree = renderer.create( - - - - - + + + + + + + ); diff --git a/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap b/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap index 12aa045c51..eab31b8055 100644 --- a/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap +++ b/frontend/src/component/strategies/StrategiesList/__snapshots__/StrategiesList.test.jsx.snap @@ -1,139 +1,42 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly with one strategy 1`] = ` -
+Array [
-

- Strategies -

-
-
- - - -
-
-
-
-
    -
  • - - - + Strategies +

- - - Gradual rollout - - - -

- Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit. -

-
-
-
-
+
+
+
    +
  • -
+
- - - - Edit strategy - - + + Gradual rollout + + - -
-
-
+
- - + + + + + - - Delete strategy - - + - -
- - -
-
+
+
+ +
+
+ +
+ + + + , +
+ Navigated to Strategies +
, +] `; exports[`renders correctly with one strategy without permissions 1`] = ` -
+Array [
-

- Strategies -

-
-
- - - -
-
-
-
-
    -
  • - - - + Strategies +
    - - - Gradual rollout - - - -

    - Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit. -

    -
    -
    -
    -
    +
    +
    +
      +
    • -
    +
    - - - - Edit strategy - - + + Gradual rollout + + - -
    -
    -
    +
    - - + + + + + - - Delete strategy - - + - -
    -
  • -
-
-
+ +
+ +
+
+ +
+ + + + , +
+ Navigated to Strategies +
, +] `; diff --git a/frontend/src/hooks/usePageTitle.ts b/frontend/src/hooks/usePageTitle.ts new file mode 100644 index 0000000000..d37600de07 --- /dev/null +++ b/frontend/src/hooks/usePageTitle.ts @@ -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'; diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts index 95929e5b39..dd8837575a 100644 --- a/frontend/src/utils/testIds.ts +++ b/frontend/src/utils/testIds.ts @@ -49,3 +49,4 @@ export const INPUT_ERROR_TEXT = 'INPUT_ERROR_TEXT'; export const HEADER_USER_AVATAR = 'HEADER_USER_AVATAR'; export const SIDEBAR_MODAL_ID = 'SIDEBAR_MODAL_ID'; export const AUTH_PAGE_ID = 'AUTH_PAGE_ID'; +export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID'; diff --git a/frontend/src/utils/testRenderer.tsx b/frontend/src/utils/testRenderer.tsx index 3e2add6003..960c992f68 100644 --- a/frontend/src/utils/testRenderer.tsx +++ b/frontend/src/utils/testRenderer.tsx @@ -3,6 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom'; import { render as rtlRender, RenderOptions } from '@testing-library/react'; import { SWRConfig } from 'swr'; import { MainThemeProvider } from 'themes/MainThemeProvider'; +import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; export const render = ( ui: JSX.Element, @@ -23,7 +24,9 @@ const Wrapper: FC = ({ children }) => { return ( new Map() }}> - {children} + + {children} + );