From 9750b273712b63d94ff29d9f396e534614f746ef Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 21 Jul 2021 14:56:56 +0200 Subject: [PATCH] SONAR-15144 autoscroll to audit category --- .../components/AuditAppRenderer.tsx | 7 +- .../AuditAppRenderer-test.tsx.snap | 4 + .../components/SubCategoryDefinitionsList.tsx | 48 ++++++-- .../components/__tests__/Definition-test.tsx | 15 +-- .../SubCategoryDefinitionsList-test.tsx | 82 ++++++++++++++ .../__snapshots__/AnalysisScope-test.tsx.snap | 2 +- .../__snapshots__/AppContainer-test.tsx.snap | 4 +- .../__snapshots__/Languages-test.tsx.snap | 2 +- .../SubCategoryDefinitionsList-test.tsx.snap | 105 ++++++++++++++++++ .../AlmTabRenderer-test.tsx.snap | 24 ++-- .../src/main/js/helpers/mocks/settings.ts | 57 ++++++++++ .../sonar-web/src/main/js/types/settings.ts | 1 + 12 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/helpers/mocks/settings.ts diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx index 31a2a546614..599d7a207a2 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx @@ -87,7 +87,12 @@ export default function AuditAppRenderer(props: AuditAppRendererProps) { values={{ housekeeping: translate('audit_logs.housekeeping_policy', housekeepingPolicy), link: ( - + {translate('audit_logs.page.description.link')} ) diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap index ca6ddeeace7..db1079e7e2a 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap @@ -38,6 +38,7 @@ exports[`should render correctly for Monthly housekeeping policy 1`] = ` style={Object {}} to={ Object { + "hash": "#auditLogs", "pathname": "/admin/settings", "query": Object { "category": "housekeeping", @@ -162,6 +163,7 @@ exports[`should render correctly for Trimestrial housekeeping policy 1`] = ` style={Object {}} to={ Object { + "hash": "#auditLogs", "pathname": "/admin/settings", "query": Object { "category": "housekeeping", @@ -298,6 +300,7 @@ exports[`should render correctly for Weekly housekeeping policy 1`] = ` style={Object {}} to={ Object { + "hash": "#auditLogs", "pathname": "/admin/settings", "query": Object { "category": "housekeeping", @@ -410,6 +413,7 @@ exports[`should render correctly for Yearly housekeeping policy 1`] = ` style={Object {}} to={ Object { + "hash": "#auditLogs", "pathname": "/admin/settings", "query": Object { "category": "housekeeping", diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx index 62e9034f9fc..f052070165b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx @@ -19,36 +19,63 @@ */ import { groupBy, isEqual, sortBy } from 'lodash'; import * as React from 'react'; +import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; +import { Location, withRouter } from '../../../components/hoc/withRouter'; import { sanitizeStringRestricted } from '../../../helpers/sanitize'; -import { Setting, SettingCategoryDefinition } from '../../../types/settings'; +import { SettingWithCategory } from '../../../types/settings'; import { getSubCategoryDescription, getSubCategoryName } from '../utils'; import DefinitionsList from './DefinitionsList'; import EmailForm from './EmailForm'; -interface Props { +export interface SubCategoryDefinitionsListProps { category: string; component?: T.Component; fetchValues: Function; - settings: Array; + location: Location; + settings: Array; subCategory?: string; } -export default class SubCategoryDefinitionsList extends React.PureComponent { +const SCROLL_OFFSET_TOP = 200; +const SCROLL_OFFSET_BOTTOM = 500; + +export class SubCategoryDefinitionsList extends React.PureComponent< + SubCategoryDefinitionsListProps +> { componentDidMount() { this.fetchValues(); } - componentDidUpdate(prevProps: Props) { + componentDidUpdate(prevProps: SubCategoryDefinitionsListProps) { const prevKeys = prevProps.settings.map(setting => setting.definition.key); const keys = this.props.settings.map(setting => setting.definition.key); if (prevProps.component !== this.props.component || !isEqual(prevKeys, keys)) { this.fetchValues(); } + + const { hash } = this.props.location; + if (hash && prevProps.location.hash !== hash) { + const element = document.querySelector(`h2[data-key=${hash.substr(1)}]`); + this.scrollToSubCategory(element); + } } + scrollToSubCategory = (element: HTMLHeadingElement | null) => { + if (element) { + const { hash } = this.props.location; + if (hash && hash.substr(1) === element.getAttribute('data-key')) { + scrollToElement(element, { + topOffset: SCROLL_OFFSET_TOP, + bottomOffset: SCROLL_OFFSET_BOTTOM, + smooth: true + }); + } + } + }; + fetchValues() { const keys = this.props.settings.map(setting => setting.definition.key).join(); - this.props.fetchValues(keys, this.props.component && this.props.component.key); + return this.props.fetchValues(keys, this.props.component && this.props.component.key); } renderEmailForm = (subCategoryKey: string) => { @@ -76,7 +103,12 @@ export default class SubCategoryDefinitionsList extends React.PureComponent {filteredSubCategories.map(subCategory => (
  • -

    {subCategory.name}

    +

    + {subCategory.name} +

    {subCategory.description != null && (
    { jest.useFakeTimers(); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx new file mode 100644 index 00000000000..3484a97bd07 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { mount, shallow } from 'enzyme'; +import * as React from 'react'; +import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { mockSettingWithCategory } from '../../../../helpers/mocks/settings'; +import { mockLocation } from '../../../../helpers/testMocks'; +import { + SubCategoryDefinitionsList, + SubCategoryDefinitionsListProps +} from '../SubCategoryDefinitionsList'; + +jest.mock('sonar-ui-common/helpers/scrolling', () => ({ + scrollToElement: jest.fn() +})); + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(''); + expect(shallowRender({ subCategory: 'qg' })).toMatchSnapshot('subcategory'); +}); + +it('should scroll if hash is defined', async () => { + const wrapper = shallowRender({ location: mockLocation({ hash: '#qg' }) }); + + await waitAndUpdate(wrapper); + + wrapper.find('h2').forEach(node => mount(node.getElement())); + + expect(scrollToElement).toBeCalled(); +}); + +it('should scroll when hash is updated', async () => { + const wrapper = shallowRender({ location: mockLocation({ hash: '#qg' }) }); + + wrapper.setProps({ location: mockLocation({ hash: '#email' }) }); + + await waitAndUpdate(wrapper); + + expect(scrollToElement).toBeCalled(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap index 070a53065bd..3bb17d7ac96 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap @@ -48,7 +48,7 @@ exports[`should render correctly 1`] = `
    - -
    @@ -252,7 +252,7 @@ exports[`should render pull request decoration binding correctly 1`] = `
    -
    diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap index b351829d1cc..8f44cf839e0 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap @@ -39,7 +39,7 @@ exports[`should render correctly 1`] = `
    -
    diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap new file mode 100644 index 00000000000..3c0d1bd5eb8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
      +
    • +

      + email +

      + + +
    • +
    • +

      + qg +

      + +
    • +
    +`; + +exports[`should render correctly: subcategory 1`] = ` +
      +
    • +

      + qg +

      + +
    • +
    +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap index a6d97e1ac03..4cdb93a9071 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap @@ -49,7 +49,7 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`]
    - @@ -106,7 +106,7 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = `
    - @@ -163,7 +163,7 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions
    - @@ -220,7 +220,7 @@ exports[`should render correctly for multi-ALM binding: loading project count 1`
    - @@ -277,7 +277,7 @@ exports[`should render correctly for single-ALM binding 1`] = `
    - @@ -334,7 +334,7 @@ exports[`should render correctly for single-ALM binding 2`] = `
    - @@ -391,7 +391,7 @@ exports[`should render correctly for single-ALM binding 3`] = `
    - @@ -438,7 +438,7 @@ exports[`should render correctly with validation: create a first 1`] = `
    - @@ -499,7 +499,7 @@ exports[`should render correctly with validation: create a second 1`] = `
    - @@ -560,7 +560,7 @@ exports[`should render correctly with validation: default 1`] = `
    - @@ -607,7 +607,7 @@ exports[`should render correctly with validation: empty 1`] = `
    - @@ -666,7 +666,7 @@ exports[`should render correctly with validation: pass the correct key for bitbu
    - diff --git a/server/sonar-web/src/main/js/helpers/mocks/settings.ts b/server/sonar-web/src/main/js/helpers/mocks/settings.ts new file mode 100644 index 00000000000..59fe8ecb25c --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/mocks/settings.ts @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { Setting, SettingWithCategory } from '../../types/settings'; + +export function mockSetting(overrides: Partial = {}): Setting { + return { + key: 'foo', + value: '42', + inherited: true, + definition: { + key: 'foo', + name: 'Foo setting', + description: 'When Foo then Bar', + type: 'INTEGER', + options: [] + }, + ...overrides + }; +} + +export function mockSettingWithCategory( + overrides: Partial = {} +): SettingWithCategory { + return { + key: 'foo', + value: '42', + inherited: true, + definition: { + key: 'foo', + name: 'Foo setting', + description: 'When Foo then Bar', + type: 'INTEGER', + options: [], + category: 'general', + fields: [], + subCategory: 'email' + }, + ...overrides + }; +} diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts index 0cf1661724b..6432a176068 100644 --- a/server/sonar-web/src/main/js/types/settings.ts +++ b/server/sonar-web/src/main/js/types/settings.ts @@ -25,6 +25,7 @@ export const enum SettingsKey { } export type Setting = SettingValue & { definition: SettingDefinition }; +export type SettingWithCategory = Setting & { definition: SettingCategoryDefinition }; export type SettingType = | 'STRING' -- 2.39.5