From 979573904e980c3aa69db9fbb8cb6a1d5a26c8e1 Mon Sep 17 00:00:00 2001 From: octavian-negru <53253211+octavian-negru@users.noreply.github.com> Date: Wed, 6 Nov 2019 17:28:38 +0200 Subject: [PATCH] Bug 1585966 - Centered header titles from Compare views should be editable --- tests/ui/perfherder/compare_table_test.jsx | 109 ++++++++++++++++- ui/css/perf.css | 10 ++ ui/perfherder/compare/ComparePageTitle.jsx | 133 +++++++++++++++++++++ ui/perfherder/compare/CompareTableView.jsx | 13 +- 4 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 ui/perfherder/compare/ComparePageTitle.jsx diff --git a/tests/ui/perfherder/compare_table_test.jsx b/tests/ui/perfherder/compare_table_test.jsx index eae104a87..775bfb774 100644 --- a/tests/ui/perfherder/compare_table_test.jsx +++ b/tests/ui/perfherder/compare_table_test.jsx @@ -9,8 +9,9 @@ import { import projects from '../mock/repositories'; import CompareTableControls from '../../../ui/perfherder/compare/CompareTableControls'; -import { compareTableText, filterText } from '../../../ui/perfherder/constants'; import CompareTable from '../../../ui/perfherder/compare/CompareTable'; +import ComparePageTitle from '../../../ui/perfherder/compare/ComparePageTitle'; +import { compareTableText, filterText } from '../../../ui/perfherder/constants'; // TODO addtional tests: // 1) that the table is receiving the correct data structure after data @@ -132,6 +133,15 @@ const compareTable = ( />, ); +const comparepageTitle = () => + render( + {}} + pageTitleQueryParam="Perfherder Compare Revisions" + />, + ); + test('toggle buttons should filter results by selected filter', async () => { const { getByText } = compareTableControls(); @@ -313,3 +323,100 @@ test('retrigger button should not appear for test with no jobs', async () => { expect(mockDataRetrigger.retriggers).toHaveLength(0); }); + +test('display of page title', async () => { + const { getAllByTitle, getAllByText } = comparepageTitle(); + + const pageTitleTitle = getAllByTitle('Click to change the page title'); + const pageTitleDefaultText = getAllByText('Perfherder Compare Revisions'); + + // title defaults to 'Perfherder Compare Revisions' + expect(pageTitleTitle[0]).toHaveTextContent('Perfherder Compare Revisions'); + expect(pageTitleDefaultText).toHaveLength(1); +}); + +test('Button hides when clicking on it and a Input is displayed', async () => { + const { queryByText, container } = comparepageTitle(); + const pageTitleDefaultText = queryByText('Perfherder Compare Revisions'); + await fireEvent.click(pageTitleDefaultText); + expect(container.firstChild).toHaveClass('input-group'); +}); + +test('clicking the title button does not change the title', async () => { + const { getByText, getByDisplayValue } = comparepageTitle(); + + const pageTitleDefaultText = await waitForElement(() => + getByText('Perfherder Compare Revisions'), + ); + + fireEvent.click(pageTitleDefaultText); + await waitForElement(() => getByDisplayValue('Perfherder Compare Revisions')); + + await waitForElement(() => getByDisplayValue('Perfherder Compare Revisions')); +}); + +test('setting a title on page updates the title accordingly', async () => { + const { getByText, getByDisplayValue } = comparepageTitle(); + + const pageTitleDefaultText = await waitForElement(() => + getByText('Perfherder Compare Revisions'), + ); + + fireEvent.click(pageTitleDefaultText); + const inputField = await waitForElement(() => + getByDisplayValue('Perfherder Compare Revisions'), + ); + fireEvent.change(inputField, { + target: { value: 'some new value' }, + }); + // pressing 'Enter' has some issues on react-testing-library; + // found workaround on https://github.com/testing-library/react-testing-library/issues/269 + fireEvent.keyPress(inputField, { key: 'Enter', keyCode: 13 }); + + // ensure this updated the title + await waitForElement(() => getByDisplayValue('some new value')); +}); + +test('re-editing the title is possible', async () => { + const { getByText, getByDisplayValue } = comparepageTitle(); + + const pageTitleDefaultText = await waitForElement(() => + getByText('Perfherder Compare Revisions'), + ); + + fireEvent.click(pageTitleDefaultText); + const inputField = await waitForElement(() => + getByDisplayValue('Perfherder Compare Revisions'), + ); + fireEvent.change(inputField, { + target: { value: 'some new value' }, + }); + fireEvent.keyPress(inputField, { key: 'Enter', keyCode: 13 }); + + await waitForElement(() => getByDisplayValue('some new value')); + fireEvent.change(inputField, { + target: { value: 'another new value' }, + }); + fireEvent.keyPress(inputField, { key: 'Enter', keyCode: 13 }); + + await waitForElement(() => getByDisplayValue('another new value')); +}); + +test("'Escape' from partially edited title does not update original title", async () => { + const { getByText, getByDisplayValue } = comparepageTitle(); + + const pageTitleDefaultText = await waitForElement(() => + getByText('Perfherder Compare Revisions'), + ); + + fireEvent.click(pageTitleDefaultText); + const inputField = await waitForElement(() => + getByDisplayValue('Perfherder Compare Revisions'), + ); + fireEvent.change(inputField, { + target: { value: 'new value' }, + }); + fireEvent.keyDown(inputField, { key: 'Escape' }); + + await waitForElement(() => getByText('Perfherder Compare Revisions')); +}); diff --git a/ui/css/perf.css b/ui/css/perf.css index f33a4f168..800d21724 100644 --- a/ui/css/perf.css +++ b/ui/css/perf.css @@ -454,3 +454,13 @@ li.pagination-active.active > button { max-width: 100%; } } + +.edit-icon { + visibility: hidden; + font-size: 14px; + vertical-align: middle; +} + +.page-title-text:hover .edit-icon { + visibility: visible; +} diff --git a/ui/perfherder/compare/ComparePageTitle.jsx b/ui/perfherder/compare/ComparePageTitle.jsx new file mode 100644 index 000000000..07d14db2a --- /dev/null +++ b/ui/perfherder/compare/ComparePageTitle.jsx @@ -0,0 +1,133 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Input, InputGroup } from 'reactstrap'; +import { faEdit } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import { replaceLocation, getAllUrlParams } from '../../helpers/location'; + +export default class ComparePageTitle extends React.Component { + constructor(props) { + super(props); + this.state = { + inEditMode: false, + pageTitle: props.pageTitleQueryParam || props.title, + newPageTitle: props.pageTitleQueryParam || props.title, + }; + } + + goToEditMode = () => { + this.setState({ + inEditMode: true, + }); + }; + + resetToDefault = async event => { + const { title } = this.props; + const { newPageTitle } = this.state || event.target.value; + this.setState({ + inEditMode: false, + pageTitle: title, + newPageTitle: title, + }); + this.changeQueryParam(newPageTitle); + }; + + editpageTitle = newPageTitle => { + this.setState({ newPageTitle }); + }; + + changeTitle = async newTitle => { + const { pageTitle } = this.state; + + this.setState({ inEditMode: false }); + if (newTitle !== pageTitle) { + this.setState({ pageTitle: newTitle }); + this.changeQueryParam(newTitle); + } + }; + + changeQueryParam = newTitle => { + const params = getAllUrlParams(); + params.set('pageTitle', newTitle); + replaceLocation(params, '/compare'); + }; + + userActionListener = async event => { + const { pageTitle } = this.state; + const { newPageTitle } = this.state || event.target.value; + + if (!newPageTitle && event.key !== 'Escape') { + this.resetToDefault(event); + } else if (event.key === 'Enter') { + this.changeTitle(newPageTitle); + } else if (event.key === 'Escape') { + this.setState({ inEditMode: false, newPageTitle: pageTitle }); + } + }; + + injectEnter = () => { + const keyboardEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + this.userActionListener(keyboardEvent); + }; + + injectEscape = () => { + const keyboardEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + this.userActionListener(keyboardEvent); + }; + + render() { + const { inEditMode, pageTitle, newPageTitle } = this.state; + + return !inEditMode ? ( + + ) : ( + + this.editpageTitle(event.target.value)} + onKeyDown={event => this.userActionListener(event)} + autoFocus + /> + + + + ); + } +} + +ComparePageTitle.propTypes = { + title: PropTypes.string.isRequired, + pageTitleQueryParam: PropTypes.string.isRequired, +}; diff --git a/ui/perfherder/compare/CompareTableView.jsx b/ui/perfherder/compare/CompareTableView.jsx index 8e21aabed..1b1aa4b27 100644 --- a/ui/perfherder/compare/CompareTableView.jsx +++ b/ui/perfherder/compare/CompareTableView.jsx @@ -22,6 +22,7 @@ import LoadingSpinner from '../../shared/LoadingSpinner'; import { scrollToLine } from '../../helpers/utils'; import RevisionInformation from './RevisionInformation'; +import ComparePageTitle from './ComparePageTitle'; import CompareTableControls from './CompareTableControls'; import NoiseTable from './NoiseTable'; @@ -189,6 +190,7 @@ export default class CompareTableView extends React.Component { newRevision, originalResultSet, newResultSet, + pageTitle, } = this.props.validated; const { @@ -274,9 +276,14 @@ export default class CompareTableView extends React.Component {

- {hasSubtests - ? `${title} subtest summary` - : 'Perfherder Compare Revisions'} +