Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to open a bot from just a URL #1232

Merged
merged 2 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions packages/app/client/src/commands/uiCommands.spec.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ export * from './botSettingsEditor/botSettingsEditorContainer';
export * from './resourcesSettings/resourcesSettingsContainer';
export * from './updateAvailableDialog';
export * from './updateUnavailableDialog';
export * from './openBotDialog/openBotDialogContainer';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

.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);
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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' });
});
});
Loading