Skip to content

Commit

Permalink
UI validation for total number of comment characters
Browse files Browse the repository at this point in the history
  • Loading branch information
js-jankisalvi committed Jul 6, 2023
1 parent a2cc9a6 commit 403772b
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 117 deletions.
199 changes: 100 additions & 99 deletions x-pack/plugins/cases/public/components/add_comment/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
*/

import React from 'react';
import { mount } from 'enzyme';
import { waitFor, act, fireEvent } from '@testing-library/react';
import { waitFor, act, fireEvent, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { noop } from 'lodash/fp';

import { noCreateCasesPermissions, TestProviders, createAppMockRenderer } from '../../common/mock';

import { CommentType } from '../../../common/api';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { SECURITY_SOLUTION_OWNER, MAX_COMMENT_LENGTH } from '../../../common/constants';
import { useCreateAttachments } from '../../containers/use_create_attachments';
import type { AddCommentProps, AddCommentRefObject } from '.';
import { AddComment } from '.';
Expand Down Expand Up @@ -52,31 +52,59 @@ const appId = 'testAppId';
const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id}.markdownEditor`;

describe('AddComment ', () => {
let appMockRender: AppMockRenderer;

beforeEach(() => {
jest.clearAllMocks();
appMockRender = createAppMockRenderer();
useCreateAttachmentsMock.mockImplementation(() => defaultResponse);
});

afterEach(() => {
sessionStorage.removeItem(draftKey);
});

it('should post comment on submit click', async () => {
const wrapper = mount(
<TestProviders>
<AddComment {...addCommentProps} />
it('renders correctly', () => {
appMockRender.render(<AddComment {...addCommentProps} />);

expect(screen.getByTestId('add-comment')).toBeInTheDocument();
});

it('should render spinner and disable submit when loading', () => {
useCreateAttachmentsMock.mockImplementation(() => ({
...defaultResponse,
isLoading: true,
}));
appMockRender.render(<AddComment {...{ ...addCommentProps, showLoading: true }} />);

expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
expect(screen.getByTestId('submit-comment')).toHaveAttribute('disabled');
});

it('should hide the component when the user does not have create permissions', () => {
useCreateAttachmentsMock.mockImplementation(() => ({
...defaultResponse,
isLoading: true,
}));

appMockRender.render(
<TestProviders permissions={noCreateCasesPermissions()}>
<AddComment {...{ ...addCommentProps }} />
</TestProviders>
);

wrapper
.find(`[data-test-subj="add-comment"] textarea`)
.first()
.simulate('change', { target: { value: sampleData.comment } });
expect(screen.queryByTestId('loading-spinner')).not.toBeInTheDocument();
});

it('should post comment on submit click', async () => {
appMockRender.render(<AddComment {...addCommentProps} />);

expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy();
const markdown = screen.getByTestId('euiMarkdownEditorTextArea');

userEvent.type(markdown, sampleData.comment);

userEvent.click(screen.getByTestId('submit-comment'));

wrapper.find(`button[data-test-subj="submit-comment"]`).first().simulate('click');
await waitFor(() => {
expect(onCommentSaving).toBeCalled();
expect(createAttachments).toBeCalledWith(
Expand All @@ -94,105 +122,49 @@ describe('AddComment ', () => {
});

await waitFor(() => {
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('');
expect(screen.getByTestId('euiMarkdownEditorTextArea')).toHaveTextContent('');
});
});

it('should render spinner and disable submit when loading', () => {
useCreateAttachmentsMock.mockImplementation(() => ({
...defaultResponse,
isLoading: true,
}));
const wrapper = mount(
<TestProviders>
<AddComment {...{ ...addCommentProps, showLoading: true }} />
</TestProviders>
);

expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy();
expect(
wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled')
).toBeTruthy();
});

it('should disable submit button when isLoading is true', () => {
useCreateAttachmentsMock.mockImplementation(() => ({
...defaultResponse,
isLoading: true,
}));
const wrapper = mount(
<TestProviders>
<AddComment {...addCommentProps} />
</TestProviders>
);

expect(
wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled')
).toBeTruthy();
});

it('should hide the component when the user does not have create permissions', () => {
useCreateAttachmentsMock.mockImplementation(() => ({
...defaultResponse,
isLoading: true,
}));
const wrapper = mount(
<TestProviders permissions={noCreateCasesPermissions()}>
<AddComment {...{ ...addCommentProps }} />
</TestProviders>
);

expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeFalsy();
});

it('should insert a quote', async () => {
const sampleQuote = 'what a cool quote \n with new lines';
const ref = React.createRef<AddCommentRefObject>();
const wrapper = mount(
<TestProviders>
<AddComment {...addCommentProps} ref={ref} />
</TestProviders>
);

wrapper
.find(`[data-test-subj="add-comment"] textarea`)
.first()
.simulate('change', { target: { value: sampleData.comment } });
appMockRender.render(<AddComment {...addCommentProps} ref={ref} />);

userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), sampleData.comment);

await act(async () => {
ref.current!.addQuote(sampleQuote);
});

expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(
`${sampleData.comment}\n\n> what a cool quote \n> with new lines \n\n`
);
await waitFor(() => {
expect(screen.getByTestId('euiMarkdownEditorTextArea').textContent).toContain(
`${sampleData.comment}\n\n> what a cool quote \n> with new lines \n\n`
);
});
});

it('should call onFocus when adding a quote', async () => {
const ref = React.createRef<AddCommentRefObject>();

mount(
<TestProviders>
<AddComment {...addCommentProps} ref={ref} />
</TestProviders>
);
appMockRender.render(<AddComment {...addCommentProps} ref={ref} />);

ref.current!.editor!.textarea!.focus = jest.fn();

await act(async () => {
ref.current!.addQuote('a comment');
});

expect(ref.current!.editor!.textarea!.focus).toHaveBeenCalled();
await waitFor(() => {
expect(ref.current!.editor!.textarea!.focus).toHaveBeenCalled();
});
});

it('should NOT call onFocus on mount', async () => {
const ref = React.createRef<AddCommentRefObject>();

mount(
<TestProviders>
<AddComment {...addCommentProps} ref={ref} />
</TestProviders>
);
appMockRender.render(<AddComment {...addCommentProps} ref={ref} />);

ref.current!.editor!.textarea!.focus = jest.fn();
expect(ref.current!.editor!.textarea!.focus).not.toHaveBeenCalled();
Expand All @@ -208,20 +180,49 @@ describe('AddComment ', () => {
const mockTimelineIntegration = { ...timelineIntegrationMock };
mockTimelineIntegration.hooks.useInsertTimeline = useInsertTimelineMock;

const wrapper = mount(
<TestProviders>
<CasesTimelineIntegrationProvider timelineIntegration={mockTimelineIntegration}>
<AddComment {...addCommentProps} />
</CasesTimelineIntegrationProvider>
</TestProviders>
appMockRender.render(
<CasesTimelineIntegrationProvider timelineIntegration={mockTimelineIntegration}>
<AddComment {...addCommentProps} />
</CasesTimelineIntegrationProvider>
);

act(() => {
attachTimeline('[title](url)');
});

await waitFor(() => {
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
expect(screen.getByTestId('euiMarkdownEditorTextArea')).toHaveTextContent('[title](url)');
});
});

describe('errors', () => {
it('shows an error when comment is empty', async () => {
appMockRender.render(<AddComment {...addCommentProps} />);

const markdown = screen.getByTestId('euiMarkdownEditorTextArea');

userEvent.clear(markdown);
userEvent.type(markdown, ' ');

await waitFor(() => {
expect(screen.getByText('Empty comments are not allowed.')).toBeInTheDocument();
});
});

it('shows an error when comment is too long', async () => {
const longComment = 'a'.repeat(MAX_COMMENT_LENGTH + 1);

appMockRender.render(<AddComment {...addCommentProps} />);

const markdown = screen.getByTestId('euiMarkdownEditorTextArea');

userEvent.paste(markdown, longComment);

await waitFor(() => {
expect(
screen.getByText('The length of the comment is too long. The maximum length is 30000.')
).toBeInTheDocument();
});
});
});
});
Expand All @@ -247,9 +248,9 @@ describe('draft comment ', () => {
});

it('should clear session storage on submit', async () => {
const result = appMockRenderer.render(<AddComment {...addCommentProps} />);
appMockRenderer.render(<AddComment {...addCommentProps} />);

fireEvent.change(result.getByLabelText('caseComment'), {
fireEvent.change(screen.getByLabelText('caseComment'), {
target: { value: sampleData.comment },
});

Expand All @@ -258,10 +259,10 @@ describe('draft comment ', () => {
});

await waitFor(() => {
expect(result.getByLabelText('caseComment')).toHaveValue(sessionStorage.getItem(draftKey));
expect(screen.getByLabelText('caseComment')).toHaveValue(sessionStorage.getItem(draftKey));
});

fireEvent.click(result.getByTestId('submit-comment'));
fireEvent.click(screen.getByTestId('submit-comment'));

await waitFor(() => {
expect(onCommentSaving).toBeCalled();
Expand All @@ -280,7 +281,7 @@ describe('draft comment ', () => {
});

await waitFor(() => {
expect(result.getByLabelText('caseComment').textContent).toBe('');
expect(screen.getByLabelText('caseComment').textContent).toBe('');
expect(sessionStorage.getItem(draftKey)).toBe('');
});
});
Expand All @@ -295,9 +296,9 @@ describe('draft comment ', () => {
});

it('should have draft comment same as existing session storage', async () => {
const result = appMockRenderer.render(<AddComment {...addCommentProps} />);
appMockRenderer.render(<AddComment {...addCommentProps} />);

expect(result.getByLabelText('caseComment')).toHaveValue('value set in storage');
expect(screen.getByLabelText('caseComment')).toHaveValue('value set in storage');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form
import { FIELD_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
import type { CommentRequestUserType } from '../../../common/api';
import { MAX_COMMENT_LENGTH } from '../../../common/constants';

import * as i18n from './translations';

const { emptyField } = fieldValidators;
const { emptyField, maxLengthField } = fieldValidators;

export interface AddCommentFormSchema {
comment: CommentRequestUserType['comment'];
Expand All @@ -25,6 +26,12 @@ export const schema: FormSchema<AddCommentFormSchema> = {
{
validator: emptyField(i18n.EMPTY_COMMENTS_NOT_ALLOWED),
},
{
validator: maxLengthField({
length: MAX_COMMENT_LENGTH,
message: i18n.MAX_LENGTH_ERROR('comment', MAX_COMMENT_LENGTH),
}),
},
],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React from 'react';
import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';

import * as i18n from '../case_view/translations';
import { MAX_COMMENT_LENGTH } from '../../../common/constants';

interface EditableMarkdownFooterProps {
handleSaveAction: () => Promise<void>;
Expand Down Expand Up @@ -42,7 +43,7 @@ const EditableMarkdownFooterComponent: React.FC<EditableMarkdownFooterProps> = (
fill
iconType="save"
onClick={handleSaveAction}
disabled={!content}
disabled={!content || content.length > MAX_COMMENT_LENGTH}
size="s"
>
{i18n.SAVE}
Expand Down
Loading

0 comments on commit 403772b

Please sign in to comment.