Skip to content

Commit

Permalink
Allow input text when exceed max length
Browse files Browse the repository at this point in the history
Signed-off-by: Lin Wang <[email protected]>
  • Loading branch information
wanglam committed Aug 13, 2024
1 parent 6c64b17 commit 696ba2f
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MAX_WORKSPACE_DESCRIPTION_LENGTH } from '../../../../common/constants';
import { WorkspaceDescriptionField } from './workspace_description_field';

describe('<WorkspaceDescriptionField />', () => {
it('should call onChange when the new value is within MAX_WORKSPACE_DESCRIPTION_LENGTH', () => {
it('should call onChange when the new value', () => {
const onChangeMock = jest.fn();
const value = 'test';

Expand All @@ -20,20 +20,24 @@ describe('<WorkspaceDescriptionField />', () => {
fireEvent.change(textarea, { target: { value: 'new value' } });

expect(onChangeMock).toHaveBeenCalledWith('new value');
});

it('should not call onChange when the new value exceeds MAX_WORKSPACE_DESCRIPTION_LENGTH', () => {
const onChangeMock = jest.fn();
const value = 'a'.repeat(MAX_WORKSPACE_DESCRIPTION_LENGTH);

render(<WorkspaceDescriptionField value={value} onChange={onChangeMock} />);

const textarea = screen.getByPlaceholderText('Describe the workspace');
fireEvent.change(textarea, {
target: { value: 'a'.repeat(MAX_WORKSPACE_DESCRIPTION_LENGTH + 1) },
});

expect(onChangeMock).not.toHaveBeenCalled();
expect(onChangeMock).toHaveBeenCalledWith('a'.repeat(MAX_WORKSPACE_DESCRIPTION_LENGTH + 1));
});

it('should render the correct number of characters left when value larger than MAX_WORKSPACE_DESCRIPTION_LENGTH', () => {
render(
<WorkspaceDescriptionField
value={'a'.repeat(MAX_WORKSPACE_DESCRIPTION_LENGTH + 1)}
onChange={jest.fn()}
/>
);

const helpText = screen.getByText(new RegExp(`-1.+characters left\.`));
expect(helpText).toBeInTheDocument();
});

it('should render the correct number of characters left when value is empty', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiCompressedFormRow, EuiCompressedTextArea } from '@elastic/eui';
import { EuiCompressedFormRow, EuiCompressedTextArea, EuiTextColor } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import React, { useCallback } from 'react';

Expand All @@ -24,13 +24,12 @@ export const WorkspaceDescriptionField = ({
}: WorkspaceDescriptionFieldProps) => {
const handleChange = useCallback(
(e) => {
const newValue = e.currentTarget.value;
if (newValue.length <= MAX_WORKSPACE_DESCRIPTION_LENGTH) {
onChange(newValue);
}
onChange(e.currentTarget.value);
},
[onChange]
);
const leftCharacters = MAX_WORKSPACE_DESCRIPTION_LENGTH - (value?.length ?? 0);
const charactersOverflow = leftCharacters < 0;

return (
<EuiCompressedFormRow
Expand All @@ -39,9 +38,18 @@ export const WorkspaceDescriptionField = ({
Description - <i>optional</i>
</>
}
isInvalid={!!error}
isInvalid={!!error || charactersOverflow}
error={error}
helpText={<>{MAX_WORKSPACE_DESCRIPTION_LENGTH - (value?.length ?? 0)} characters left.</>}
helpText={
<EuiTextColor color={charactersOverflow ? 'danger' : 'subdued'}>
{i18n.translate('workspace.form.description.charactersLeft', {
defaultMessage: '{leftCharacters} characters left.',
values: {
leftCharacters,
},
})}
</EuiTextColor>
}
>
<EuiCompressedTextArea
value={value}
Expand All @@ -52,7 +60,6 @@ export const WorkspaceDescriptionField = ({
defaultMessage: 'Describe the workspace',
})}
readOnly={readOnly}
maxLength={MAX_WORKSPACE_DESCRIPTION_LENGTH}
/>
</EuiCompressedFormRow>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MAX_WORKSPACE_NAME_LENGTH } from '../../../../common/constants';
import { WorkspaceNameField } from './workspace_name_field';

describe('<WorkspaceNameField />', () => {
it('should call onChange when the new value is within MAX_WORKSPACE_NAME_LENGTH', () => {
it('should call onChange when the new value', () => {
const onChangeMock = jest.fn();
const value = 'test';

Expand All @@ -20,18 +20,19 @@ describe('<WorkspaceNameField />', () => {
fireEvent.change(input, { target: { value: 'new value' } });

expect(onChangeMock).toHaveBeenCalledWith('new value');
});

it('should not call onChange when the new value exceeds MAX_WORKSPACE_NAME_LENGTH', () => {
const onChangeMock = jest.fn();
const value = 'a'.repeat(MAX_WORKSPACE_NAME_LENGTH);
fireEvent.change(input, { target: { value: 'a'.repeat(MAX_WORKSPACE_NAME_LENGTH + 1) } });

render(<WorkspaceNameField value={value} onChange={onChangeMock} />);
expect(onChangeMock).toHaveBeenCalledWith('a'.repeat(MAX_WORKSPACE_NAME_LENGTH + 1));
});

const input = screen.getByPlaceholderText('Enter a name');
fireEvent.change(input, { target: { value: 'a'.repeat(MAX_WORKSPACE_NAME_LENGTH + 1) } });
it('should render the correct number of characters left when value greater than MAX_WORKSPACE_NAME_LENGTH', () => {
render(
<WorkspaceNameField value={'a'.repeat(MAX_WORKSPACE_NAME_LENGTH + 1)} onChange={jest.fn()} />
);

expect(onChangeMock).not.toHaveBeenCalled();
const helpText = screen.getByText(new RegExp(`-1.+characters left\.`));
expect(helpText).toBeInTheDocument();
});

it('should render the correct number of characters left when value is empty', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiCompressedFieldText, EuiCompressedFormRow } from '@elastic/eui';
import { EuiCompressedFieldText, EuiCompressedFormRow, EuiTextColor } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import React, { useCallback } from 'react';

Expand All @@ -24,13 +24,12 @@ export const WorkspaceNameField = ({
}: WorkspaceNameFieldProps) => {
const handleChange = useCallback(
(e) => {
const newValue = e.currentTarget.value;
if (newValue.length <= MAX_WORKSPACE_NAME_LENGTH) {
onChange(newValue);
}
onChange(e.currentTarget.value);
},
[onChange]
);
const leftCharacters = MAX_WORKSPACE_NAME_LENGTH - (value?.length ?? 0);
const charactersOverflow = leftCharacters < 0;

return (
<EuiCompressedFormRow
Expand All @@ -39,14 +38,22 @@ export const WorkspaceNameField = ({
})}
helpText={
<>
{MAX_WORKSPACE_NAME_LENGTH - (value?.length ?? 0)} characters left. <br />
<EuiTextColor color={charactersOverflow ? 'danger' : 'subdued'}>
{i18n.translate('workspace.form.name.charactersLeft', {
defaultMessage: '{leftCharacters} characters left.',
values: {
leftCharacters,
},
})}
</EuiTextColor>
<br />
{i18n.translate('workspace.form.workspaceDetails.name.helpText', {
defaultMessage:
'Use a unique name for the workspace. Valid characters are a-z, A-Z, 0-9, (), [], _ (underscore), - (hyphen) and (space).',
})}
</>
}
isInvalid={!!error}
isInvalid={!!error || charactersOverflow}
error={error}
>
<EuiCompressedFieldText
Expand All @@ -57,7 +64,6 @@ export const WorkspaceNameField = ({
placeholder={i18n.translate('workspace.form.workspaceDetails.name.placeholder', {
defaultMessage: 'Enter a name',
})}
maxLength={MAX_WORKSPACE_NAME_LENGTH}
/>
</EuiCompressedFormRow>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
*/

import { useCallback, useState, FormEventHandler, useRef, useMemo } from 'react';
import {
htmlIdGenerator,
EuiFieldTextProps,
EuiTextAreaProps,
EuiColorPickerProps,
} from '@elastic/eui';
import { htmlIdGenerator, EuiColorPickerProps } from '@elastic/eui';

import { useApplications } from '../../hooks';
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import { applicationServiceMock } from '../../../../../core/public/mocks';
import {
MAX_WORKSPACE_DESCRIPTION_LENGTH,
MAX_WORKSPACE_NAME_LENGTH,
} from '../../../common/constants';
import { WorkspaceCreateActionPanel } from './workspace_create_action_panel';

const mockApplication = applicationServiceMock.createStartContract();

describe('WorkspaceCreateActionPanel', () => {
const formId = 'workspaceForm';
const formData = {
name: 'Test Workspace',
description: 'This is a test workspace',
};

it('should disable the "Create Workspace" button when name exceeds the maximum length', () => {
const longName = 'a'.repeat(MAX_WORKSPACE_NAME_LENGTH + 1);
render(
<WorkspaceCreateActionPanel
formId={formId}
formData={{ name: longName, description: formData.description }}
application={mockApplication}
/>
);
const createButton = screen.getByText('Create workspace');
expect(createButton.closest('button')).toBeDisabled();
});

it('should disable the "Create Workspace" button when description exceeds the maximum length', () => {
const longDescription = 'a'.repeat(MAX_WORKSPACE_DESCRIPTION_LENGTH + 1);
render(
<WorkspaceCreateActionPanel
formId={formId}
formData={{ name: formData.name, description: longDescription }}
application={mockApplication}
/>
);
const createButton = screen.getByText('Create workspace');
expect(createButton.closest('button')).toBeDisabled();
});

it('should enable the "Create Workspace" button when name and description are within the maximum length', () => {
render(
<WorkspaceCreateActionPanel
formId={formId}
formData={formData}
application={mockApplication}
/>
);
const createButton = screen.getByText('Create workspace');
expect(createButton.closest('button')).not.toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@
import { EuiSmallButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import React, { useState, useCallback } from 'react';
import { ApplicationStart } from 'opensearch-dashboards/public';
import type { ApplicationStart } from 'opensearch-dashboards/public';
import type { WorkspaceFormData } from './types';
import { WorkspaceCancelModal } from './workspace_cancel_modal';
import {
MAX_WORKSPACE_DESCRIPTION_LENGTH,
MAX_WORKSPACE_NAME_LENGTH,
} from '../../../common/constants';

interface WorkspaceCreateActionPanelProps {
formId: string;
formData: Partial<Pick<WorkspaceFormData, 'name' | 'description'>>;
application: ApplicationStart;
}

export const WorkspaceCreateActionPanel = ({
formId,
formData,
application,
}: WorkspaceCreateActionPanelProps) => {
const [isCancelModalVisible, setIsCancelModalVisible] = useState(false);
const closeCancelModal = useCallback(() => setIsCancelModalVisible(false), []);
const showCancelModal = useCallback(() => setIsCancelModalVisible(true), []);
const createButtonDisabled =
(formData.name?.length ?? 0) > MAX_WORKSPACE_NAME_LENGTH ||
(formData.description?.length ?? 0) > MAX_WORKSPACE_DESCRIPTION_LENGTH;

return (
<>
Expand All @@ -41,6 +51,7 @@ export const WorkspaceCreateActionPanel = ({
type="submit"
form={formId}
data-test-subj="workspaceForm-bottomBar-createButton"
disabled={createButtonDisabled}
>
{i18n.translate('workspace.form.bottomBar.createWorkspace', {
defaultMessage: 'Create workspace',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const WorkspaceForm = (props: WorkspaceFormProps) => {
)}
<EuiSpacer />
{operationType === WorkspaceOperationType.Create && (
<WorkspaceCreateActionPanel formId={formId} application={application} />
<WorkspaceCreateActionPanel formData={formData} formId={formId} application={application} />
)}
{operationType === WorkspaceOperationType.Update && (
<WorkspaceBottomBar
Expand Down

0 comments on commit 696ba2f

Please sign in to comment.