Skip to content

Commit

Permalink
Merge 69441f5 into 76468bd
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Wilaby committed Jan 16, 2019
2 parents 76468bd + 69441f5 commit 6738889
Show file tree
Hide file tree
Showing 30 changed files with 1,412 additions and 430 deletions.
24 changes: 15 additions & 9 deletions packages/app/client/src/commands/uiCommands.spec.ts
Expand Up @@ -10,23 +10,22 @@ jest.mock('../ui/dialogs', () => ({
}
}
));
import { EditorActions, OpenEditorAction } from '../data/action/editorActions';
import * as Constants from '../constants';
import { SharedConstants } from '@bfemulator/app-shared';
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
import { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } from '../constants';
import { AzureAuthAction, AzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
import { EditorActions, OpenEditorAction } from '../data/action/editorActions';
import { NavBarActions, SelectNavBarAction } from '../data/action/navBarActions';
import * as editorHelpers from '../data/editorHelpers';
import { store } from '../data/store';
import {
AzureLoginPromptDialogContainer,
AzureLoginSuccessDialogContainer,
BotCreationDialog,
DialogService,
DialogService, OpenBotDialogContainer,
SecretPromptDialogContainer
} from '../ui/dialogs';
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
import { SharedConstants } from '@bfemulator/app-shared';
import { registerCommands } from './uiCommands';
import * as editorHelpers from '../data/editorHelpers';
import { store } from '../data/store';
import { AzureAuthAction, AzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';

const Commands = SharedConstants.Commands.UI;

Expand Down Expand Up @@ -57,6 +56,13 @@ describe('the uiCommands', () => {
expect(result).toBe(true);
});

it('should call DialogService.showDialog when the ShowOpenBotDialog command is dispatched', async () => {
const spy = jest.spyOn(DialogService, 'showDialog');
const result = await registry.getCommand(Commands.ShowOpenBotDialog).handler();
expect(spy).toHaveBeenCalledWith(OpenBotDialogContainer);
expect(result).toBe(true);
});

describe('should dispatch the appropriate action to the store', () => {
it('when the SwitchNavBarTab command is dispatched', () => {
let arg: SelectNavBarAction = {} as SelectNavBarAction;
Expand All @@ -65,7 +71,7 @@ describe('the uiCommands', () => {
expect(arg.type).toBe(NavBarActions.select);
expect(arg.payload.selection).toBe('Do it Nauuuw!');
});

it('when the ShowAppSettings command is dispatched', () => {
let arg: OpenEditorAction = {} as OpenEditorAction;
store.dispatch = action => (arg as any) = action;
Expand Down
33 changes: 20 additions & 13 deletions packages/app/client/src/commands/uiCommands.ts
Expand Up @@ -31,30 +31,31 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import { SharedConstants } from '@bfemulator/app-shared';
import { CommandRegistry } from '@bfemulator/sdk-shared';
import { ServiceTypes } from 'botframework-config/lib/schema';
import * as Constants from '../constants';
import { azureArmTokenDataChanged, beginAzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
import * as EditorActions from '../data/action/editorActions';
import * as NavBarActions from '../data/action/navBarActions';
import { ProgressIndicatorPayload, updateProgressIndicator } from '../data/action/progressIndicatorActions';
import { switchTheme } from '../data/action/themeActions';
import { showWelcomePage } from '../data/editorHelpers';
import { AzureAuthState } from '../data/reducer/azureAuthReducer';
import { store } from '../data/store';
import {
AzureLoginFailedDialogContainer,
AzureLoginPromptDialogContainer,
AzureLoginSuccessDialogContainer,
BotCreationDialog,
DialogService,
OpenBotDialogContainer,
PostMigrationDialogContainer,
ProgressIndicatorContainer,
SecretPromptDialogContainer,
UpdateAvailableDialogContainer,
UpdateUnavailableDialogContainer,
ProgressIndicatorContainer
UpdateUnavailableDialogContainer
} from '../ui/dialogs';
import { store } from '../data/store';
import * as EditorActions from '../data/action/editorActions';
import * as NavBarActions from '../data/action/navBarActions';
import * as Constants from '../constants';
import { CommandRegistry } from '@bfemulator/sdk-shared';
import { ServiceTypes } from 'botframework-config/lib/schema';
import { SharedConstants } from '@bfemulator/app-shared';
import { azureArmTokenDataChanged, beginAzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
import { AzureAuthState } from '../data/reducer/azureAuthReducer';
import { ProgressIndicatorPayload, updateProgressIndicator } from '../data/action/progressIndicatorActions';
import { switchTheme } from '../data/action/themeActions';

/** Register UI commands (toggling UI) */
export function registerCommands(commandRegistry: CommandRegistry) {
Expand All @@ -72,6 +73,12 @@ export function registerCommands(commandRegistry: CommandRegistry) {
return await DialogService.showDialog(BotCreationDialog);
});

// ---------------------------------------------------------------------------
// Shows a bot creation dialog
commandRegistry.registerCommand(UI.ShowOpenBotDialog, async () => {
return await DialogService.showDialog(OpenBotDialogContainer);
});

// ---------------------------------------------------------------------------
// Shows a dialog prompting the user for a bot secret
commandRegistry.registerCommand(UI.ShowSecretPromptDialog, async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/app/client/src/ui/dialogs/index.ts
Expand Up @@ -47,3 +47,4 @@ export * from './botSettingsEditor/botSettingsEditorContainer';
export * from './resourcesSettings/resourcesSettingsContainer';
export * from './updateAvailableDialog';
export * from './updateUnavailableDialog';
export * from './openBotDialog/openBotDialogContainer';
@@ -0,0 +1,68 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

.browse-button {
overflow: hidden;
position: absolute;
right: 0;
bottom: 12px;
}

.input-container {
padding-right: 115px;

& + div {
margin-left: 8px;
}
}

.file-input {
position: absolute;
left: 0;
height: 100%;
z-index: 2;
opacity: 0;
}

/* maintains "light" theme for the recent bots list since it's in a modal */
.theme-overrides {
--my-bots-entry-bg: var(--neutral-4);
--my-bots-entry-border: 1px solid transparent;
--tab-icon-color: var(--neutral-12);
--my-bots-path-color: var(--neutral-12);
--focused-selected-list-item-bg: #006AB1;
--my-bots-well-bg: var(--neutral-3);
--my-bots-well-border: 0;
--my-bots-scrollbar-color: rgba(136, 136, 136, 0.5);
--my-bots-entry-color: var(--neutral-12);
}
@@ -0,0 +1,5 @@
// This is a generated file. Changes are likely to result in being overwritten
export const browseButton: string;
export const inputContainer: string;
export const fileInput: string;
export const themeOverrides: string;
@@ -0,0 +1,196 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import { newNotification, SharedConstants } from '@bfemulator/app-shared';
import { mount } from 'enzyme';
import * as React from 'react';
import { Provider } from 'react-redux';
import { combineReducers, createStore } from 'redux';
import * as BotActions from '../../../data/action/botActions';
import { beginAdd } from '../../../data/action/notificationActions';
import { bot } from '../../../data/reducer/bot';
import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl';
import { ActiveBotHelper } from '../../helpers/activeBotHelper';
import { DialogService } from '../service';
import { OpenBotDialog } from './openBotDialog';
import { OpenBotDialogContainer } from './openBotDialogContainer';

const mockStore = createStore(combineReducers({ bot }));
jest.mock('./openBotDialog.scss', () => ({}));
jest.mock('../../../data/store', () => ({
get store() {
return mockStore;
}
}));
jest.mock('../service', () => ({
DialogService: {
showDialog: () => Promise.resolve(true),
hideDialog: () => Promise.resolve(false),
}
}));
jest.mock('../dialogStyles.scss', () => ({}));
jest.mock('../../editor/recentBotsList/recentBotsList.scss', () => ({}));
jest.mock('../', () => ({}));

const bots = [
{
'path': '/some/path',
'displayName': 'mockMock',
'transcriptsPath': '/Users/microsoft/Documents/testbot/transcripts',
'chatsPath': '/Users/microsoft/Documents/testbot/dialogs'
}
];

describe('The OpenBotDialog', () => {
let mockDispatch;
let node;
let parent;
let instance;
beforeEach(() => {
mockStore.dispatch(BotActions.load(bots));
mockDispatch = jest.spyOn(mockStore, 'dispatch');
parent = mount(<Provider store={ mockStore }>
<OpenBotDialogContainer/>
</Provider>);
node = parent.find(OpenBotDialog);
instance = node.instance();
});

it('should hide the dialog when cancel is clicked', () => {
const spy = jest.spyOn(DialogService, 'hideDialog');
instance.props.onDialogCancel();
expect(spy).toHaveBeenCalled();
});

it('should orchestrate the appropriate sequences when a recent bot is clicked', async () => {
const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'call').mockResolvedValue(true);
const dialogSpy = jest.spyOn(DialogService, 'hideDialog');
await instance.onBotSelected(bots[0]);
const { Switch } = SharedConstants.Commands.Bot;
expect(commandServiceSpy).toHaveBeenCalledWith(Switch, '/some/path');
expect(dialogSpy).toHaveBeenCalled();
});

it('should send a notification when the bot fails to open', async () => {
await instance.onBotSelected(null);
const message = `An Error occurred on the Open Bot Dialog: TypeError: Cannot read property 'path' of null`;
const notification = newNotification(message);
const action = beginAdd(notification);
notification.timestamp = jasmine.any(Number) as any;
notification.id = jasmine.any(String) as any;
expect(mockDispatch).toHaveBeenLastCalledWith(action);
});

it('should make the appropriate calls when onCreateNewBotClick in called', async () => {
const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'call').mockResolvedValue(true);
const dialogSpy = jest.spyOn(DialogService, 'hideDialog');

await instance.onCreateNewBotClick();
expect(commandServiceSpy).toHaveBeenLastCalledWith(SharedConstants.Commands.UI.ShowBotCreationDialog);
expect(dialogSpy).toHaveBeenCalled();
});

it('should send a notification when onCreateNewBotClick fails', async () => {
const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'call').mockRejectedValue('oh noes!');
await instance.onCreateNewBotClick();
const message = `An Error occurred on the Open Bot Dialog: oh noes!`;
const notification = newNotification(message);
const action = beginAdd(notification);
notification.timestamp = jasmine.any(Number) as any;
notification.id = jasmine.any(String) as any;
expect(mockDispatch).toHaveBeenLastCalledWith(action);

expect(commandServiceSpy).toHaveBeenLastCalledWith(SharedConstants.Commands.UI.ShowBotCreationDialog);
});

it('should properly set the state when the input changes', () => {
instance.onInputChange({
target: {
type: 'text',
value: 'http://localhost:6500/api/messages'
}
} as any);

expect(instance.state.botUrl).toBe('http://localhost:6500/api/messages');

instance.onInputChange({
target: {
type: 'file',
files: { item: () => ({ path: 'some/path/to/myBot.bot' }) }
}
} as any);

expect(instance.state.botUrl).toBe('some/path/to/myBot.bot');
});

it('should select all text in the input when focused', () => {
const spy = jest.fn();
const mockInput = {
value: 'this is some text',
setSelectionRange: spy
};

instance.onFocus({ target: mockInput } as any);

expect(spy).toHaveBeenCalledWith(0, 17);
});

it('should open a bot when a path is provided', async () => {
instance.onInputChange({
target: {
type: 'file',
files: { item: () => ({ path: 'some/path/to/myBot.bot' }) }
}
} as any);

const botHelperSpy = jest.spyOn(ActiveBotHelper, 'confirmAndOpenBotFromFile').mockResolvedValue(true);
await instance.onOpenClick();

expect(botHelperSpy).toHaveBeenCalledWith('some/path/to/myBot.bot');
});

it('should open an endpoint when a URL is provided', async () => {
instance.onInputChange({
target: {
type: 'text',
value: 'http://localhost:6500/api/messages'
}
} as any);

const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'call');
await instance.onOpenClick();

expect(commandServiceSpy).toHaveBeenCalledWith(SharedConstants.Commands.Emulator.NewLiveChat,
{ endpoint: 'http://localhost:6500/api/messages' });
});
});

0 comments on commit 6738889

Please sign in to comment.