Skip to content

Commit

Permalink
Change: Refactor DialogNotification into a function component
Browse files Browse the repository at this point in the history
The DialogNotification component is intended to show messages for
example errors in a dialog easily. With this change it got refactored to
a function component which is easier to understand. Also tests for the
DialogNotification component are added.
  • Loading branch information
bjoernricks committed Apr 12, 2024
1 parent 2c07b19 commit 589fd7a
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 89 deletions.
14 changes: 12 additions & 2 deletions src/web/components/dialog/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,25 @@ export const DialogFooterLayout = styled(Layout)`
padding: 10px 20px 10px 20px;
`;

const DialogFooter = ({title, onClick, loading = false}) => (
<DialogFooterLayout align={['end', 'center']} shrink="0">
const DialogFooter = ({
title,
onClick,
loading = false,
'data-testid': dataTestId,
}) => (
<DialogFooterLayout
align={['end', 'center']}
shrink="0"
data-testid={dataTestId}
>
<Button onClick={onClick} title={title} loading={loading}>
{title}
</Button>
</DialogFooterLayout>
);

DialogFooter.propTypes = {
'data-testid': PropTypes.string,
loading: PropTypes.bool,
title: PropTypes.string.isRequired,
onClick: PropTypes.func,
Expand Down
171 changes: 171 additions & 0 deletions src/web/components/notification/__tests__/dialognotification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import React from 'react';

import {rendererWith, screen, fireEvent} from 'web/utils/testing';

import DialogNotification from '../dialognotification';
import useDialogNotification from '../useDialogNotification';

const TestComponent = () => {
const {
dialogState,
closeDialog,
showMessage,
showError,
showErrorMessage,
showSuccessMessage,
} = useDialogNotification();
return (
<div>
<button data-testid="show-message" onClick={() => showMessage('foo')} />
<button
data-testid="show-message-with-subject"
onClick={() => showMessage('foo', 'bar')}
/>
<button
data-testid="show-error-message"
onClick={() => showErrorMessage('foo')}
/>
<button
data-testid="show-success-message"
onClick={() => showSuccessMessage('foo')}
/>
<button
data-testid="show-error"
onClick={() => showError(new Error('foo'))}
/>
<DialogNotification {...dialogState} onCloseClick={closeDialog} />
</div>
);
};

describe('DialogNotification tests', () => {
test('should display a message', () => {
const {render} = rendererWith();

render(<TestComponent />);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

const button = screen.getByTestId('show-message');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('Message');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');
});

test('should display a message with subject', () => {
const {render} = rendererWith();

render(<TestComponent />);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

const button = screen.getByTestId('show-message-with-subject');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('bar');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');
});

test('should allow to close dialog', () => {
const {render} = rendererWith();

render(<TestComponent />);

const button = screen.getByTestId('show-message');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('Message');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');

const dialogFooter = screen.getByTestId('dialog-notification-footer');
const closeButton = dialogFooter.querySelector('button');

fireEvent.click(closeButton);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

test('should display an error message', () => {
const {render} = rendererWith();

render(<TestComponent />);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

const button = screen.getByTestId('show-error-message');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('Error');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');
});

test('should display a success message', () => {
const {render} = rendererWith();

render(<TestComponent />);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

const button = screen.getByTestId('show-success-message');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('Success');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');
});

test('should display an error', () => {
const {render} = rendererWith();

render(<TestComponent />);

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

const button = screen.getByTestId('show-error');

fireEvent.click(button);

const dialogTitleBar = screen.getByTestId('dialog-title-bar');

expect(dialogTitleBar).toHaveTextContent('Error');

const dialogContent = screen.getByTestId('dialog-notification-message');

expect(dialogContent).toHaveTextContent('foo');
});
});
112 changes: 31 additions & 81 deletions src/web/components/notification/dialognotification.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,100 +17,50 @@
*/
import React from 'react';

import _ from 'gmp/locale';

import {isDefined} from 'gmp/utils/identity';
import {hasValue} from 'gmp/utils/identity';

import PropTypes from 'web/utils/proptypes';

import useTranslation from 'web/hooks/useTranslation';

import Dialog from 'web/components/dialog/dialog';
import DialogContent from 'web/components/dialog/content';
import DialogFooter from 'web/components/dialog/footer';
import DialogTitle from 'web/components/dialog/title';
import ScrollableContent from 'web/components/dialog/scrollablecontent';

class DialogNotification extends React.Component {
constructor(...args) {
super(...args);

this.handleDialogClose = this.handleDialogClose.bind(this);
this.handleShowError = this.handleShowError.bind(this);
this.handleShowErrorMessage = this.handleShowErrorMessage.bind(this);
this.handleShowMessage = this.handleShowMessage.bind(this);
this.handleShowSuccessMessage = this.handleShowSuccessMessage.bind(this);

this.state = {};
}

handleShowError(error) {
this.handleShowErrorMessage(error.message);
}

handleShowErrorMessage(message) {
this.handleShowMessage(message, _('Error'));
}

handleShowSuccessMessage(message) {
this.handleShowMessage(message, _('Success'));
}
const DialogNotification = ({title, message, onCloseClick}) => {
const [_] = useTranslation();

handleShowMessage(message, subject = _('Message')) {
this.setState({
message,
title: subject,
});
if (!hasValue(message)) {
return null;
}

handleDialogClose() {
this.setState({message: undefined});
}

isDialogOpen() {
return isDefined(this.state.message);
}

render() {
const {children} = this.props;

if (!isDefined(children)) {
return null;
}

const {title, message} = this.state;
return (
<React.Fragment>
{children({
showError: this.handleShowError,
showErrorMessage: this.handleShowErrorMessage,
showMessage: this.handleShowMessage,
showSuccessMessage: this.handleShowSuccessMessage,
})}
{this.isDialogOpen() && (
<Dialog width="400px" onClose={this.handleDialogClose}>
{({close, moveProps, heightProps}) => (
<DialogContent>
<DialogTitle
title={title}
onCloseClick={close}
{...moveProps}
/>
<ScrollableContent {...heightProps}>
{message}
</ScrollableContent>
<DialogFooter title={_('Close')} onClick={close} />
</DialogContent>
)}
</Dialog>
)}
</React.Fragment>
);
}
}
return (
<Dialog width="400px" onClose={onCloseClick}>
{({close, moveProps, heightProps}) => (
<DialogContent>
<DialogTitle title={title} onCloseClick={close} {...moveProps} />
<ScrollableContent
{...heightProps}
data-testid="dialog-notification-message"
>
{message}
</ScrollableContent>
<DialogFooter
title={_('Close')}
onClick={close}
data-testid="dialog-notification-footer"
/>
</DialogContent>
)}
</Dialog>
);
};

DialogNotification.propTypes = {
children: PropTypes.func,
message: PropTypes.string,
title: PropTypes.string,
onCloseClick: PropTypes.func,
};

export default DialogNotification;

// vim: set ts=2 sw=2 tw=80:
Loading

0 comments on commit 589fd7a

Please sign in to comment.