[Cases] UI validations for max description characters, max tag characters and maximum tags per case (#161087)

## Summary

This PR adds UI validations for

- maximum 30000 characters per description
- maximum 256 characters per tag
- maximum 200 tags per case

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

**Flaky test runner:**

https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2555

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Janki Salvi 2023-07-06 12:12:26 +02:00 committed by GitHub
parent 8543d5f94b
commit 97dd41fc61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 419 additions and 130 deletions

View file

@ -6,28 +6,9 @@
*/
import { MAX_ASSIGNEES_PER_CASE } from '../constants';
import { isInvalidTag, areTotalAssigneesInvalid } from './validators';
import { areTotalAssigneesInvalid } from './validators';
describe('validators', () => {
describe('isInvalidTag', () => {
it('validates a whitespace correctly', () => {
expect(isInvalidTag(' ')).toBe(true);
});
it('validates an empty string correctly', () => {
expect(isInvalidTag('')).toBe(true);
});
it('returns false if the string is not empty', () => {
expect(isInvalidTag('string')).toBe(false);
});
it('returns false if the string contains spaces', () => {
// Ending space has been put intentionally
expect(isInvalidTag('my string ')).toBe(false);
});
});
describe('areTotalAssigneesInvalid', () => {
const generateAssignees = (num: number) =>
Array.from(Array(num).keys()).map((uid) => {

View file

@ -8,8 +8,6 @@
import type { CaseAssignees } from '../api';
import { MAX_ASSIGNEES_PER_CASE } from '../constants';
export const isInvalidTag = (value: string) => value.trim() === '';
export const areTotalAssigneesInvalid = (assignees?: CaseAssignees): boolean => {
if (assignees == null) {
return false;

View file

@ -295,6 +295,12 @@ export const MAX_LENGTH_ERROR = (field: string, length: number) =>
defaultMessage: 'The length of the {field} is too long. The maximum length is {length}.',
});
export const MAX_TAGS_ERROR = (length: number) =>
i18n.translate('xpack.cases.createCase.maxTagsError', {
values: { length },
defaultMessage: 'Too many tags. The maximum number of allowed tags is {length}',
});
export const LINK_APPROPRIATE_LICENSE = i18n.translate('xpack.cases.common.appropriateLicense', {
defaultMessage: 'appropriate license',
});

View file

@ -6,32 +6,18 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { waitFor, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { EditTagsProps } from './edit_tags';
import { EditTags } from './edit_tags';
import { getFormMock } from '../../__mock__/form';
import { readCasesPermissions, TestProviders } from '../../../common/mock';
import { waitFor } from '@testing-library/react';
import { useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib/hooks/use_form';
import { readCasesPermissions, TestProviders, createAppMockRenderer } from '../../../common/mock';
import type { AppMockRenderer } from '../../../common/mock';
import { useGetTags } from '../../../containers/use_get_tags';
import { MAX_LENGTH_PER_TAG } from '../../../../common/constants';
jest.mock('@kbn/es-ui-shared-plugin/static/forms/hook_form_lib/hooks/use_form');
jest.mock('../../../containers/use_get_tags');
jest.mock(
'@kbn/es-ui-shared-plugin/static/forms/hook_form_lib/components/form_data_provider',
() => ({
FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) =>
children({ tags: ['rad', 'dude'] }),
})
);
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
EuiFieldText: () => <input />,
};
});
const onSubmit = jest.fn();
const defaultProps: EditTagsProps = {
isLoading: false,
@ -40,80 +26,123 @@ const defaultProps: EditTagsProps = {
};
describe('EditTags ', () => {
let appMockRender: AppMockRenderer;
const sampleTags = ['coke', 'pepsi'];
const fetchTags = jest.fn();
const formHookMock = getFormMock({ tags: sampleTags });
beforeEach(() => {
jest.resetAllMocks();
(useForm as jest.Mock).mockImplementation(() => ({ form: formHookMock }));
(useGetTags as jest.Mock).mockImplementation(() => ({
data: sampleTags,
refetch: fetchTags,
}));
appMockRender = createAppMockRenderer();
});
it('Renders no tags, and then edit', () => {
const wrapper = mount(
<TestProviders>
<EditTags {...defaultProps} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="no-tags"]`).last().exists()).toBeTruthy();
wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click');
expect(wrapper.find(`[data-test-subj="no-tags"]`).last().exists()).toBeFalsy();
expect(wrapper.find(`[data-test-subj="edit-tags"]`).last().exists()).toBeTruthy();
it('renders no tags, and then edit', async () => {
appMockRender.render(<EditTags {...defaultProps} />);
expect(screen.getByTestId('no-tags')).toBeInTheDocument();
userEvent.click(screen.getByTestId('tag-list-edit-button'));
await waitFor(() => {
expect(screen.queryByTestId('no-tags')).not.toBeInTheDocument();
expect(screen.getByTestId('edit-tags')).toBeInTheDocument();
});
});
it('Edit tag on submit', async () => {
const wrapper = mount(
<TestProviders>
<EditTags {...defaultProps} />
</TestProviders>
);
wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click');
wrapper.find(`[data-test-subj="edit-tags-submit"]`).last().simulate('click');
await waitFor(() => expect(onSubmit).toBeCalledWith(sampleTags));
it('edit tag from options on submit', async () => {
appMockRender.render(<EditTags {...defaultProps} />);
userEvent.click(screen.getByTestId('tag-list-edit-button'));
userEvent.type(screen.getByRole('combobox'), `${sampleTags[0]}{enter}`);
userEvent.click(screen.getByTestId('edit-tags-submit'));
await waitFor(() => expect(onSubmit).toBeCalledWith([sampleTags[0]]));
});
it('Tag options render with new tags added', () => {
const wrapper = mount(
<TestProviders>
<EditTags {...defaultProps} />
</TestProviders>
);
wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click');
expect(
wrapper.find(`[data-test-subj="caseTags"] [data-test-subj="input"]`).first().prop('options')
).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]);
it('add new tags on submit', async () => {
appMockRender.render(<EditTags {...defaultProps} />);
userEvent.click(screen.getByTestId('tag-list-edit-button'));
await waitFor(() => {
expect(screen.getByTestId('edit-tags')).toBeInTheDocument();
});
userEvent.type(screen.getByRole('combobox'), 'dude{enter}');
userEvent.click(screen.getByTestId('edit-tags-submit'));
await waitFor(() => expect(onSubmit).toBeCalledWith(['dude']));
});
it('Cancels on cancel', () => {
const props = {
...defaultProps,
tags: ['pepsi'],
};
const wrapper = mount(
<TestProviders>
<EditTags {...props} />
</TestProviders>
);
it('cancels on cancel', async () => {
appMockRender.render(<EditTags {...defaultProps} />);
expect(wrapper.find(`[data-test-subj="tag-pepsi"]`).last().exists()).toBeTruthy();
wrapper.find(`[data-test-subj="tag-list-edit-button"]`).last().simulate('click');
userEvent.click(screen.getByTestId('tag-list-edit-button'));
expect(wrapper.find(`[data-test-subj="tag-pepsi"]`).last().exists()).toBeFalsy();
wrapper.find(`[data-test-subj="edit-tags-cancel"]`).last().simulate('click');
wrapper.update();
expect(wrapper.find(`[data-test-subj="tag-pepsi"]`).last().exists()).toBeTruthy();
userEvent.type(screen.getByRole('combobox'), 'new{enter}');
await waitFor(() => {
expect(screen.getByTestId('comboBoxInput')).toHaveTextContent('new');
});
userEvent.click(screen.getByTestId('edit-tags-cancel'));
await waitFor(() => {
expect(onSubmit).not.toBeCalled();
expect(screen.getByTestId('no-tags')).toBeInTheDocument();
});
});
it('shows error when tag is empty', async () => {
appMockRender.render(<EditTags {...defaultProps} />);
userEvent.click(screen.getByTestId('tag-list-edit-button'));
await waitFor(() => {
expect(screen.getByTestId('edit-tags')).toBeInTheDocument();
});
userEvent.type(screen.getByRole('combobox'), ' {enter}');
await waitFor(() => {
expect(screen.getByText('A tag must contain at least one non-space character.'));
});
});
it('shows error when tag is too long', async () => {
const longTag = 'z'.repeat(MAX_LENGTH_PER_TAG + 1);
appMockRender.render(<EditTags {...defaultProps} />);
userEvent.click(screen.getByTestId('tag-list-edit-button'));
await waitFor(() => {
expect(screen.getByTestId('edit-tags')).toBeInTheDocument();
});
userEvent.paste(screen.getByRole('combobox'), `${longTag}`);
userEvent.keyboard('{enter}');
await waitFor(() => {
expect(screen.getByText('The length of the tag is too long. The maximum length is 256.'));
});
});
it('does not render when the user does not have update permissions', () => {
const wrapper = mount(
appMockRender.render(
<TestProviders permissions={readCasesPermissions()}>
<EditTags {...defaultProps} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="tag-list-edit"]`).exists()).toBeFalsy();
expect(screen.queryByTestId('tag-list-edit')).not.toBeInTheDocument();
});
});

View file

@ -79,6 +79,7 @@ const CategoryFormFieldComponent: React.FC<Props> = ({
label={CATEGORY}
error={errorMessage}
isInvalid={isInvalid}
data-test-subj="case-create-form-category"
fullWidth
>
<CategoryComponent

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { waitFor } from '@testing-library/react';
import { waitFor, screen } from '@testing-library/react';
import userEvent, { specialChars } from '@testing-library/user-event';
import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
@ -16,6 +16,7 @@ import type { FormProps } from './schema';
import { schema } from './schema';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import { MAX_DESCRIPTION_LENGTH } from '../../../common/constants';
describe('Description', () => {
let globalForm: FormHook;
@ -45,24 +46,26 @@ describe('Description', () => {
});
it('it renders', async () => {
const result = appMockRender.render(
appMockRender.render(
<MockHookWrapperComponent>
<Description {...defaultProps} />
</MockHookWrapperComponent>
);
expect(result.getByTestId('caseDescription')).toBeInTheDocument();
expect(screen.getByTestId('caseDescription')).toBeInTheDocument();
});
it('it changes the description', async () => {
const result = appMockRender.render(
appMockRender.render(
<MockHookWrapperComponent>
<Description {...defaultProps} />
</MockHookWrapperComponent>
);
const description = screen.getByTestId('euiMarkdownEditorTextArea');
userEvent.type(
result.getByRole('textbox'),
description,
`${specialChars.selectAll}${specialChars.delete}My new description`
);
@ -70,4 +73,41 @@ describe('Description', () => {
expect(globalForm.getFormData()).toEqual({ description: 'My new description' });
});
});
it('shows an error when description is empty', async () => {
appMockRender.render(
<MockHookWrapperComponent>
<Description {...defaultProps} />
</MockHookWrapperComponent>
);
const description = screen.getByTestId('euiMarkdownEditorTextArea');
userEvent.clear(description);
userEvent.type(description, ' ');
await waitFor(() => {
expect(screen.getByText('A description is required.')).toBeInTheDocument();
});
});
it('shows an error when description is too long', async () => {
const longDescription = 'a'.repeat(MAX_DESCRIPTION_LENGTH + 1);
appMockRender.render(
<MockHookWrapperComponent>
<Description {...defaultProps} />
</MockHookWrapperComponent>
);
const description = screen.getByTestId('euiMarkdownEditorTextArea');
userEvent.paste(description, longDescription);
await waitFor(() => {
expect(
screen.getByText('The length of the description is too long. The maximum length is 30000.')
).toBeInTheDocument();
});
});
});

View file

@ -9,14 +9,22 @@ import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form
import { FIELD_TYPES, VALIDATION_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 { CasePostRequest, ConnectorTypeFields } from '../../../common/api';
import { isInvalidTag } from '../../../common/utils/validators';
import { MAX_TITLE_LENGTH } from '../../../common/constants';
import {
MAX_TITLE_LENGTH,
MAX_DESCRIPTION_LENGTH,
MAX_LENGTH_PER_TAG,
MAX_TAGS_PER_CASE,
} from '../../../common/constants';
import * as i18n from './translations';
import { OptionalFieldLabel } from './optional_field_label';
import { SEVERITY_TITLE } from '../severity/translations';
const { emptyField, maxLengthField } = fieldValidators;
const isInvalidTag = (value: string) => value.trim() === '';
const isTagCharactersInLimit = (value: string) => value.trim().length > MAX_LENGTH_PER_TAG;
export const schemaTags = {
type: FIELD_TYPES.COMBO_BOX,
label: i18n.TAGS,
@ -37,6 +45,29 @@ export const schemaTags = {
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: false,
},
{
validator: ({ value }: { value: string | string[] }) => {
if (
(!Array.isArray(value) && isTagCharactersInLimit(value)) ||
(Array.isArray(value) && value.length > 0 && value.some(isTagCharactersInLimit))
) {
return {
message: i18n.MAX_LENGTH_ERROR('tag', MAX_LENGTH_PER_TAG),
};
}
},
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: false,
},
{
validator: ({ value }: { value: string[] }) => {
if (Array.isArray(value) && value.length > MAX_TAGS_PER_CASE) {
return {
message: i18n.MAX_TAGS_ERROR(MAX_TAGS_PER_CASE),
};
}
},
},
],
};
@ -69,6 +100,12 @@ export const schema: FormSchema<FormProps> = {
{
validator: emptyField(i18n.DESCRIPTION_REQUIRED),
},
{
validator: maxLengthField({
length: MAX_DESCRIPTION_LENGTH,
message: i18n.MAX_LENGTH_ERROR('description', MAX_DESCRIPTION_LENGTH),
}),
},
],
},
selectedOwner: {

View file

@ -6,18 +6,18 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { EuiComboBox } from '@elastic/eui';
import { waitFor } from '@testing-library/react';
import { waitFor, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { Tags } from './tags';
import type { FormProps } from './schema';
import { schema } from './schema';
import { TestProviders } from '../../common/mock';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer, TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { MAX_LENGTH_PER_TAG } from '../../../common/constants';
jest.mock('../../common/lib/kibana');
jest.mock('../../containers/use_get_tags');
@ -26,6 +26,7 @@ const useGetTagsMock = useGetTags as jest.Mock;
describe('Tags', () => {
let globalForm: FormHook;
let appMockRender: AppMockRenderer;
const MockHookWrapperComponent: React.FC = ({ children }) => {
const { form } = useForm<FormProps>({
@ -47,45 +48,62 @@ describe('Tags', () => {
beforeEach(() => {
jest.clearAllMocks();
useGetTagsMock.mockReturnValue({ data: ['test'] });
appMockRender = createAppMockRenderer();
});
it('it renders', async () => {
const wrapper = mount(
appMockRender.render(
<MockHookWrapperComponent>
<Tags isLoading={false} />
</MockHookWrapperComponent>
);
await waitFor(() => {
expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy();
expect(screen.getByTestId('caseTags')).toBeInTheDocument();
});
});
it('it disables the input when loading', async () => {
const wrapper = mount(
<MockHookWrapperComponent>
<Tags isLoading={true} />
</MockHookWrapperComponent>
);
expect(wrapper.find(EuiComboBox).prop('disabled')).toBeTruthy();
});
it('it changes the tags', async () => {
const wrapper = mount(
appMockRender.render(
<MockHookWrapperComponent>
<Tags isLoading={false} />
</MockHookWrapperComponent>
);
await waitFor(() => {
(
wrapper.find(EuiComboBox).props() as unknown as {
onChange: (a: EuiComboBoxOptionOption[]) => void;
}
).onChange(['test', 'case'].map((tag) => ({ label: tag })));
});
userEvent.type(screen.getByRole('combobox'), 'test{enter}');
userEvent.type(screen.getByRole('combobox'), 'case{enter}');
expect(globalForm.getFormData()).toEqual({ tags: ['test', 'case'] });
});
it('it shows error when tag is empty', async () => {
appMockRender.render(
<MockHookWrapperComponent>
<Tags isLoading={false} />
</MockHookWrapperComponent>
);
userEvent.type(screen.getByRole('combobox'), ' {enter}');
await waitFor(() => {
expect(screen.getByText('A tag must contain at least one non-space character.'));
});
});
it('it shows error when tag is too long', async () => {
const longTag = 'z'.repeat(MAX_LENGTH_PER_TAG + 1);
appMockRender.render(
<MockHookWrapperComponent>
<Tags isLoading={false} />
</MockHookWrapperComponent>
);
userEvent.paste(screen.getByRole('combobox'), `${longTag}`);
userEvent.keyboard('{enter}');
await waitFor(() => {
expect(screen.getByText('The length of the tag is too long. The maximum length is 256.'));
});
});
});

View file

@ -14,6 +14,7 @@ import { basicCase } from '../../containers/mock';
import { Description } from '.';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer, noUpdateCasesPermissions, TestProviders } from '../../common/mock';
import { MAX_DESCRIPTION_LENGTH } from '../../../common/constants';
jest.mock('../../common/lib/kibana');
jest.mock('../../common/navigation/hooks');
@ -109,6 +110,42 @@ describe('Description', () => {
});
});
it('shows an error when description is empty', async () => {
const res = appMockRender.render(
<Description {...defaultProps} onUpdateField={onUpdateField} />
);
userEvent.click(res.getByTestId('description-edit-icon'));
userEvent.clear(screen.getByTestId('euiMarkdownEditorTextArea'));
userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), '');
await waitFor(() => {
expect(screen.getByText('A description is required.')).toBeInTheDocument();
});
});
it('shows an error when description is too long', async () => {
const longDescription = Array(MAX_DESCRIPTION_LENGTH / 2 + 1)
.fill('a')
.toString();
const res = appMockRender.render(
<Description {...defaultProps} onUpdateField={onUpdateField} />
);
userEvent.click(res.getByTestId('description-edit-icon'));
userEvent.clear(screen.getByTestId('euiMarkdownEditorTextArea'));
userEvent.paste(screen.getByTestId('euiMarkdownEditorTextArea'), longDescription);
await waitFor(() => {
expect(
screen.getByText('The length of the description is too long. The maximum length is 30000.')
).toBeInTheDocument();
});
});
it('should hide the edit button when the user does not have update permissions', () => {
appMockRender.render(
<TestProviders permissions={noUpdateCasesPermissions()}>

View file

@ -8,9 +8,10 @@
import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
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 { MAX_DESCRIPTION_LENGTH } from '../../../common/constants';
import * as i18n from '../../common/translations';
const { emptyField } = fieldValidators;
const { emptyField, maxLengthField } = fieldValidators;
export interface Content {
content: string;
}
@ -19,7 +20,13 @@ export const schema: FormSchema<Content> = {
type: FIELD_TYPES.TEXTAREA,
validations: [
{
validator: emptyField(i18n.REQUIRED_FIELD),
validator: emptyField(i18n.DESCRIPTION_REQUIRED),
},
{
validator: maxLengthField({
length: MAX_DESCRIPTION_LENGTH,
message: i18n.MAX_LENGTH_ERROR('description', MAX_DESCRIPTION_LENGTH),
}),
},
],
},

View file

@ -218,6 +218,30 @@ describe('EditableTitle', () => {
);
});
it('does not submit the title is empty', () => {
const wrapper = mount(
<TestProviders>
<EditableTitle {...defaultProps} />
</TestProviders>
);
wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click');
wrapper.update();
wrapper
.find('input[data-test-subj="editable-title-input-field"]')
.simulate('change', { target: { value: '' } });
wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click');
wrapper.update();
expect(wrapper.find('.euiFormErrorText').text()).toBe('A name is required.');
expect(submitTitle).not.toHaveBeenCalled();
expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(
false
);
});
it('does not show an error after a previous edit error was displayed', () => {
const longTitle = 'a'.repeat(161);

View file

@ -57,7 +57,12 @@ const EditableTitleComponent: React.FC<EditableTitleProps> = ({ onSubmit, isLoad
const onClickEditIcon = useCallback(() => setEditMode(true), []);
const onClickSubmit = useCallback((): void => {
if (newTitle.length > MAX_TITLE_LENGTH) {
if (!newTitle.trim().length) {
setErrors([i18n.TITLE_REQUIRED]);
return;
}
if (newTitle.trim().length > MAX_TITLE_LENGTH) {
setErrors([i18n.MAX_LENGTH_ERROR('title', MAX_TITLE_LENGTH)]);
return;
}
@ -69,10 +74,10 @@ const EditableTitleComponent: React.FC<EditableTitleProps> = ({ onSubmit, isLoad
setErrors([]);
}, [newTitle, onSubmit, title]);
const handleOnChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => setNewTitle(e.target.value),
[]
);
const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setNewTitle(e.target.value);
setErrors([]);
}, []);
const hasErrors = errors.length > 0;

View file

@ -20,6 +20,7 @@ export function CasesCommonServiceProvider({ getService, getPageObject }: FtrPro
const common = getPageObject('common');
const toasts = getService('toasts');
const retry = getService('retry');
const comboBox = getService('comboBox');
return {
/**
@ -121,5 +122,15 @@ export function CasesCommonServiceProvider({ getService, getPageObject }: FtrPro
await header.waitUntilLoadingHasFinished();
},
async addMultipleTags(tags: string[]) {
await testSubjects.click('tag-list-edit-button');
for (const [index, tag] of tags.entries()) {
await comboBox.setCustom('comboBoxInput', `${tag}-${index}`);
}
await header.waitUntilLoadingHasFinished();
},
};
}

View file

@ -66,6 +66,39 @@ export default ({ getService, getPageObject }: FtrProviderContext) => {
expect(await button.getVisibleText()).equal('Add connector');
});
it('displays errors correctly while creating a case', async () => {
const caseTitle = Array(161).fill('x').toString();
const longTag = Array(256).fill('a').toString();
const longCategory = Array(51).fill('x').toString();
await cases.create.openCreateCasePage();
await cases.create.createCase({
title: caseTitle,
description: '',
tag: longTag,
severity: CaseSeverity.HIGH,
category: longCategory,
});
const title = await find.byCssSelector('[data-test-subj="caseTitle"]');
expect(await title.getVisibleText()).contain(
'The length of the name is too long. The maximum length is 160.'
);
const description = await testSubjects.find('caseDescription');
expect(await description.getVisibleText()).contain('A description is required.');
const tags = await testSubjects.find('caseTags');
expect(await tags.getVisibleText()).contain(
'The length of the tag is too long. The maximum length is 256.'
);
const category = await testSubjects.find('case-create-form-category');
expect(await category.getVisibleText()).contain(
'The length of the category is too long. The maximum length is 50.'
);
});
describe('Assignees', function () {
before(async () => {
await createUsersAndRoles(getService, users, roles);

View file

@ -73,6 +73,21 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await find.byCssSelector('[data-test-subj*="title-update-action"]');
});
it('shows error message when title is more than 160 characters', async () => {
const longTitle = Array(161).fill('x').toString();
await testSubjects.click('editable-title-edit-icon');
await testSubjects.setValue('editable-title-input-field', longTitle);
await testSubjects.click('editable-title-submit-btn');
const error = await find.byCssSelector('.euiFormErrorText');
expect(await error.getVisibleText()).equal(
'The length of the title is too long. The maximum length is 160.'
);
await testSubjects.click('editable-title-cancel-btn');
});
it('adds a comment to a case', async () => {
const commentArea = await find.byCssSelector(
'[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
@ -112,6 +127,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await find.byCssSelector('[data-test-subj*="category-delete-action"]');
});
it('shows error when category is more than 50 characters', async () => {
const longCategory = Array(51).fill('x').toString();
await testSubjects.click('category-edit-button');
await comboBox.setCustom('comboBoxInput', longCategory);
await testSubjects.click('edit-category-submit');
const error = await find.byCssSelector('.euiFormErrorText');
expect(await error.getVisibleText()).equal(
'The length of the category is too long. The maximum length is 50.'
);
await testSubjects.click('edit-category-cancel');
});
it('adds a tag to a case', async () => {
const tag = uuidv4();
await testSubjects.click('tag-list-edit-button');
@ -125,6 +154,25 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await find.byCssSelector('[data-test-subj*="tags-add-action"]');
});
it('shows error when tag is more than 256 characters', async () => {
const longTag = Array(257).fill('a').toString();
await testSubjects.click('tag-list-edit-button');
await comboBox.clearInputField('comboBoxInput');
await header.waitUntilLoadingHasFinished();
await comboBox.setCustom('comboBoxInput', longTag);
await browser.pressKeys(browser.keys.ENTER);
const error = await find.byCssSelector('.euiFormErrorText');
expect(await error.getVisibleText()).equal(
'The length of the tag is too long. The maximum length is 256.'
);
await testSubjects.click('edit-tags-cancel');
});
it('deletes a tag from a case', async () => {
await testSubjects.click('tag-list-edit-button');
// find the tag button and click the close button
@ -136,6 +184,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await find.byCssSelector('[data-test-subj*="tags-delete-action"]');
});
it('shows error when more than 200 tags are added to the case', async () => {
const tags = Array(200).fill('foo');
await cases.common.addMultipleTags(tags);
await testSubjects.click('edit-tags-submit');
const error = await find.byCssSelector('.euiFormErrorText');
expect(await error.getVisibleText()).equal(
'Too many tags. The maximum number of allowed tags is 200'
);
await testSubjects.click('edit-tags-cancel');
});
describe('status', () => {
it('changes a case status to in-progress via dropdown menu', async () => {
await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']);