mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
8543d5f94b
commit
97dd41fc61
15 changed files with 419 additions and 130 deletions
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,6 +79,7 @@ const CategoryFormFieldComponent: React.FC<Props> = ({
|
|||
label={CATEGORY}
|
||||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
data-test-subj="case-create-form-category"
|
||||
fullWidth
|
||||
>
|
||||
<CategoryComponent
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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.'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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()}>
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue