diff --git a/Composer/packages/client/__tests__/components/CreationFlow/DefineConversation/index.test.tsx b/Composer/packages/client/__tests__/components/CreationFlow/DefineConversation/index.test.tsx index 9d6305c0f2..3a01c93751 100644 --- a/Composer/packages/client/__tests__/components/CreationFlow/DefineConversation/index.test.tsx +++ b/Composer/packages/client/__tests__/components/CreationFlow/DefineConversation/index.test.tsx @@ -2,17 +2,18 @@ // Licensed under the MIT License. import * as React from 'react'; -import { render, fireEvent } from '@bfc/test-utils'; +import { fireEvent } from '@bfc/test-utils'; -import { StoreContext } from '../../../../src/store'; +import { renderWithStore } from '../../../testUtils'; import { StorageFolder } from '../../../../src/store/types'; import DefineConversation from '../../../../src/components/CreationFlow/DefineConversation'; describe('', () => { - let onSubmitMock; - let onDismissMock; const onCurrentPathUpdateMock = jest.fn(); - let component, storeContext, saveTemplateMock, locationMock; + const saveTemplateMock = jest.fn(); + const onSubmitMock = jest.fn(); + const onDismissMock = jest.fn(); + let storeContext, locationMock; const focusedStorageFolder: StorageFolder = { name: 'Desktop', parent: '/test-folder', @@ -30,49 +31,44 @@ describe('', () => { ], }; function renderComponent() { - return render( - - - + return renderWithStore( + , + storeContext.state, + storeContext.action ); } beforeEach(() => { - saveTemplateMock = jest.fn(); locationMock = {}; storeContext = { actions: { saveTemplateId: saveTemplateMock, }, state: { - templateId: '', + templateId: 'EchoBot', focusedStorageFolder: '', + storages: [], }, }; - - onSubmitMock = jest.fn(); }); it('should render the component', () => { - storeContext.state.storages = []; - component = renderComponent(); + const component = renderComponent(); expect(component.container).toBeDefined(); }); it('should update formdata with data passed through location props', async () => { - storeContext.state.storages = []; - storeContext.state.templateId = 'EchoBot'; locationMock = { search: 'schemaUrl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fbotframework-sdk%2Fmaster%2Fschemas%2Fcomponent%2Fcomponent.schema%26name%3DEchoBot-11299%26description%3DTest%20Echo', }; - component = renderComponent(); + const component = renderComponent(); const node = await component.findByText('Next'); fireEvent.click(node); expect(onSubmitMock).toHaveBeenCalledWith({ @@ -82,4 +78,12 @@ describe('', () => { 'https://raw.githubusercontent.com/microsoft/botframework-sdk/master/schemas/component/component.schema', }); }); + + it('does not allow submission when the name is invalid', async () => { + const component = renderComponent(); + const nameField = await component.getByTestId('NewDialogName'); + fireEvent.change(nameField, { target: { value: 'invalidName;' } }); + const node = await component.getByTestId('SubmitNewBotBtn'); + expect(node).toBeDisabled(); + }); }); diff --git a/Composer/packages/client/__tests__/components/createDialogModal.test.tsx b/Composer/packages/client/__tests__/components/createDialogModal.test.tsx new file mode 100644 index 0000000000..c03806050c --- /dev/null +++ b/Composer/packages/client/__tests__/components/createDialogModal.test.tsx @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as React from 'react'; +import { fireEvent } from '@bfc/test-utils'; + +import { CreateDialogModal } from '../../src/pages/design/createDialogModal'; +import { renderWithStore } from '../testUtils'; + +describe('', () => { + const onSubmitMock = jest.fn(); + const onDismissMock = jest.fn(); + let storeContext; + function renderComponent() { + return renderWithStore( + , + storeContext.state + ); + } + + beforeEach(() => { + storeContext = { + state: { + showCreateDialogModal: true, + dialogs: [], + }, + }; + }); + + it('should render the component', () => { + const component = renderComponent(); + expect(component.container).toBeDefined(); + }); + + it('does not allow submission when the name is invalid', async () => { + const component = renderComponent(); + const nameField = await component.getByTestId('NewDialogName'); + fireEvent.change(nameField, { target: { value: 'invalidName;' } }); + const node = await component.getByTestId('SubmitNewDialogBtn'); + expect(node).toBeDisabled(); + }); +}); diff --git a/Composer/packages/client/src/components/CreationFlow/DefineConversation/index.tsx b/Composer/packages/client/src/components/CreationFlow/DefineConversation/index.tsx index 786585c983..d827348dae 100644 --- a/Composer/packages/client/src/components/CreationFlow/DefineConversation/index.tsx +++ b/Composer/packages/client/src/components/CreationFlow/DefineConversation/index.tsx @@ -14,7 +14,7 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; -import { DialogCreationCopy } from '../../../constants'; +import { DialogCreationCopy, nameRegex } from '../../../constants'; import { DialogWrapper } from '../../DialogWrapper'; import { DialogTypes } from '../../DialogWrapper/styles'; import { LocationSelectContent } from '../LocationBrowser/LocationSelectContent'; @@ -67,11 +67,8 @@ const DefineConversation: React.FC = (props) => { name: { required: true, validate: (value) => { - const nameRegex = /^[a-zA-Z0-9-_.]+$/; if (!value || !nameRegex.test(value)) { - return formatMessage( - 'Spaces and special characters are not allowed. Use letters, numbers, -, or _., numbers, -, and _' - ); + return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); } const newBotPath = diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index 528ad955b7..4e519dad7c 100644 --- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx @@ -19,6 +19,7 @@ import { DialogInfo, SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; +import { nameRegex } from '../../constants'; import { generateNewDialog, getTriggerTypes, @@ -37,7 +38,6 @@ import { StoreContext } from '../../store'; import { styles, dropdownStyles, dialogWindow, intent } from './styles'; -const nameRegex = /^[a-zA-Z0-9-_.]+$/; const validateForm = ( selectedType: string, data: TriggerFormData, @@ -64,9 +64,7 @@ const validateForm = ( } if (selectedType === intentTypeKey && (!intent || !nameRegex.test(intent))) { - errors.intent = formatMessage( - 'Spaces and special characters are not allowed. Use letters, numbers, -, or _., numbers, -, and _' - ); + errors.intent = formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); } if (selectedType === intentTypeKey && isRegEx && regExIntents.find((ri) => ri.intent === intent)) { diff --git a/Composer/packages/client/src/constants/index.ts b/Composer/packages/client/src/constants/index.ts index 576f2761b6..c7b28f69ad 100644 --- a/Composer/packages/client/src/constants/index.ts +++ b/Composer/packages/client/src/constants/index.ts @@ -255,3 +255,5 @@ export const DefaultPublishConfig = { }; export const EmptyBotTemplateId = 'EmptyBot'; + +export const nameRegex = /^[a-zA-Z0-9-_]+$/; diff --git a/Composer/packages/client/src/pages/design/createDialogModal.tsx b/Composer/packages/client/src/pages/design/createDialogModal.tsx index e14cc013ad..48979039bd 100644 --- a/Composer/packages/client/src/pages/design/createDialogModal.tsx +++ b/Composer/packages/client/src/pages/design/createDialogModal.tsx @@ -7,7 +7,7 @@ import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog'; import { Stack, StackItem } from 'office-ui-fabric-react/lib/Stack'; import { TextField } from 'office-ui-fabric-react/lib/TextField'; -import { DialogCreationCopy } from '../../constants'; +import { DialogCreationCopy, nameRegex } from '../../constants'; import { DialogWrapper } from '../../components/DialogWrapper'; import { DialogTypes } from '../../components/DialogWrapper/styles'; import { StorageFolder } from '../../store/types'; @@ -37,8 +37,6 @@ export const CreateDialogModal: React.FC = (props) => { name: { required: true, validate: (value) => { - const nameRegex = /^[a-zA-Z0-9-_.]+$/; - if (!nameRegex.test(value)) { return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); } @@ -104,7 +102,12 @@ export const CreateDialogModal: React.FC = (props) => { - + diff --git a/Composer/packages/client/src/pages/language-understanding/publish-luis-modal.jsx b/Composer/packages/client/src/pages/language-understanding/publish-luis-modal.jsx index 6af198edbc..42f0429e69 100644 --- a/Composer/packages/client/src/pages/language-understanding/publish-luis-modal.jsx +++ b/Composer/packages/client/src/pages/language-understanding/publish-luis-modal.jsx @@ -18,7 +18,7 @@ import keys from 'lodash/keys'; import { StoreContext } from '../../store'; -import { Text, Tips, Links } from './../../constants'; +import { Text, Tips, Links, nameRegex } from './../../constants'; import { textFieldLabel, dialog, dialogModal, dialogSubTitle, dialogContent, consoleStyle } from './styles'; const STATE = { @@ -38,7 +38,6 @@ const onRenderLabel = (info) => (props) => ( ); -const nameRegex = /^[a-zA-Z0-9-_.]+$/; const validationProperties = ['name', 'authoringKey', 'environment']; const defaultFields = { authoringRegion: 'westus', defaultLanguage: 'en-us' }; const validateForm = (data) => { @@ -49,7 +48,7 @@ const validateForm = (data) => { const value = data[key]; if (validationProperties.includes(key) && (!value || !nameRegex.test(value))) { result.errors[key] = formatMessage( - 'Spaces and special characters are not allowed. Use letters, numbers, -, or _., numbers, -, and _' + 'Spaces and special characters are not allowed. Use letters, numbers, -, or _.' ); } else if (key in defaultFields && value === '') { result[key] = defaultFields[key];