diff --git a/packages/app/client/src/commands/uiCommands.spec.ts b/packages/app/client/src/commands/uiCommands.spec.ts index 6fe74b41a..11bf4afae 100644 --- a/packages/app/client/src/commands/uiCommands.spec.ts +++ b/packages/app/client/src/commands/uiCommands.spec.ts @@ -30,9 +30,8 @@ // 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 { SharedConstants } from '@bfemulator/app-shared'; +import { DebugMode, SharedConstants } from '@bfemulator/app-shared'; import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; -import { DebugMode } from '@bfemulator/app-shared'; import { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } from '../constants'; import { AzureAuthAction, AzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions'; @@ -164,4 +163,64 @@ describe('the uiCommands', () => { expect(type).toEqual(dispatchedActions[index].type) ); }); + + describe('when showing the markdow page', () => { + it('should detect when the user is offline', async () => { + const dispatchedActions = []; + store.dispatch = action => { + dispatchedActions.push(action); + return action; + }; + await registry + .getCommand(Commands.ShowMarkdownPage) + .handler('http://localhost', 'Yo!', { navigator: { onLine: false } }); + expect(dispatchedActions.length).toBe(1); + expect(dispatchedActions[0].payload.meta).toEqual({ + markdown: '', + label: 'Yo!', + onLine: false, + }); + }); + + it('should detect when the user has no internet even though they are connected to a network', async () => { + const dispatchedActions = []; + store.dispatch = action => { + dispatchedActions.push(action); + return action; + }; + jest.spyOn(CommandServiceImpl, 'remoteCall').mockRejectedValueOnce('oh noes! ENOTFOUND'); + await registry + .getCommand(Commands.ShowMarkdownPage) + .handler('http://localhost', 'Yo!', { navigator: { onLine: true } }); + expect(dispatchedActions.length).toBe(1); + expect(dispatchedActions[0].payload.meta).toEqual({ + markdown: '', + label: 'Yo!', + onLine: false, + }); + }); + + it('should attempt to fetch the markdown and decode it from a Uint8Array', async () => { + const dispatchedActions = []; + store.dispatch = action => { + dispatchedActions.push(action); + return action; + }; + jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValueOnce(true); + await registry + .getCommand(Commands.ShowMarkdownPage) + .handler('http://localhost', 'Yo!', { navigator: { onLine: true } }); + expect(dispatchedActions.length).toBe(1); + expect(dispatchedActions[0].payload).toEqual({ + contentType: 'application/vnd.microsoft.bfemulator.document.markdown', + documentId: 'markdown-page', + isGlobal: true, + meta: { + markdown: 'Hi! I am in your decode', + label: 'Yo!', + onLine: true, + }, + }); + }); + }); }); diff --git a/packages/app/client/src/commands/uiCommands.ts b/packages/app/client/src/commands/uiCommands.ts index f53e0744c..4213e633b 100644 --- a/packages/app/client/src/commands/uiCommands.ts +++ b/packages/app/client/src/commands/uiCommands.ts @@ -76,29 +76,32 @@ export function registerCommands(commandRegistry: CommandRegistry) { // --------------------------------------------------------------------------- // Shows the markdown page after retrieving the remote source - commandRegistry.registerCommand(UI.ShowMarkdownPage, async (urlOrMarkdown: string, label: string) => { - let markdown = ''; - let { onLine } = navigator; - if (!onLine) { - return showMarkdownPage(markdown, label, onLine); - } - try { - new URL(urlOrMarkdown); // Is this a valid URL? - const bytes: ArrayBuffer = await CommandServiceImpl.remoteCall( - SharedConstants.Commands.Electron.FetchRemote, - urlOrMarkdown - ); - markdown = new TextDecoder().decode(bytes); - } catch (e) { - if (typeof e === 'string' && ('' + e).includes('ENOTFOUND')) { - onLine = false; - } else { - // assume this is markdown text - markdown = urlOrMarkdown; + commandRegistry.registerCommand( + UI.ShowMarkdownPage, + async (urlOrMarkdown: string, label: string, windowRef = window) => { + let markdown = ''; + let { onLine } = windowRef.navigator; + if (!onLine) { + return showMarkdownPage(markdown, label, onLine); } + try { + new URL(urlOrMarkdown); // Is this a valid URL? + const bytes: ArrayBuffer = await CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.FetchRemote, + urlOrMarkdown + ); + markdown = new TextDecoder().decode(bytes); + } catch (e) { + if (typeof e === 'string' && ('' + e).includes('ENOTFOUND')) { + onLine = false; + } else { + // assume this is markdown text + markdown = urlOrMarkdown; + } + } + return showMarkdownPage(markdown, label, onLine); } - return showMarkdownPage(markdown, label, onLine); - }); + ); // --------------------------------------------------------------------------- // Shows a bot creation dialog diff --git a/packages/app/client/src/ui/editor/welcomePage/welcomePage.scss b/packages/app/client/src/ui/editor/welcomePage/welcomePage.scss index 9cd14b10f..28968dce9 100644 --- a/packages/app/client/src/ui/editor/welcomePage/welcomePage.scss +++ b/packages/app/client/src/ui/editor/welcomePage/welcomePage.scss @@ -184,7 +184,7 @@ } .bot-inspector-header-container { - margin: 15px; + margin: 15px 0; display: flex; > h1 { diff --git a/packages/app/client/src/ui/editor/welcomePage/welcomePage.spec.tsx b/packages/app/client/src/ui/editor/welcomePage/welcomePage.spec.tsx index efae006b2..c0faffbf6 100644 --- a/packages/app/client/src/ui/editor/welcomePage/welcomePage.spec.tsx +++ b/packages/app/client/src/ui/editor/welcomePage/welcomePage.spec.tsx @@ -108,8 +108,8 @@ describe('The AzureLoginFailedDialogContainer component should', () => { instance.props.openBotInspectorDocs(); expect(callSpy).toHaveBeenCalledWith( SharedConstants.Commands.UI.ShowMarkdownPage, - 'https://raw.githubusercontent.com/Microsoft/BotFramework-Emulator/master/content/CHANNELS.md', - 'Bot Inspector Help' + SharedConstants.Channels.ReadmeUrl, + SharedConstants.Channels.HelpLabel ); }); }); diff --git a/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.spec.tsx b/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.spec.tsx index c603610d5..04c78bfdd 100644 --- a/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.spec.tsx +++ b/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.spec.tsx @@ -36,12 +36,14 @@ import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import { SharedConstants } from '@bfemulator/app-shared'; +import { MouseEvent } from 'react'; import { enable } from '../../../../data/action/presentationActions'; import { setActiveTab, appendTab, splitTab } from '../../../../data/action/editorActions'; import { CONTENT_TYPE_APP_SETTINGS, CONTENT_TYPE_LIVE_CHAT, + CONTENT_TYPE_MARKDOWN, CONTENT_TYPE_TRANSCRIPT, CONTENT_TYPE_WELCOME_PAGE, } from '../../../../constants'; @@ -181,26 +183,26 @@ describe('TabBar', () => { }); it('should handle a tab click', () => { - instance.handleTabClick(0); + instance.handleTabClick({ currentTarget: { dataset: { index: 0 } } } as any); expect(mockDispatch).toHaveBeenCalledWith(setActiveTab('doc1')); }); it('should handle a key press', () => { - const mockOtherKeyPress = { key: 'a' }; - const mockSpaceKeyPress = { key: ' ' }; - const mockEnterKeyPress = { key: 'enter' }; + const mockOtherKeyPress = { key: 'a', currentTarget: { dataset: { index: 0 } } as any }; + const mockSpaceKeyPress = { key: ' ', currentTarget: { dataset: { index: 0 } } as any }; + const mockEnterKeyPress = { key: 'enter', currentTarget: { dataset: { index: 0 } } as any }; // simulate neither key press - instance.handleKeyDown(mockOtherKeyPress, 0); + instance.handleKeyDown(mockOtherKeyPress); expect(mockDispatch).not.toHaveBeenCalled(); // simulate space key press - instance.handleKeyDown(mockSpaceKeyPress, 0); + instance.handleKeyDown(mockSpaceKeyPress); expect(mockDispatch).toHaveBeenCalledWith(setActiveTab('doc1')); // simulate enter key press - instance.handleKeyDown(mockEnterKeyPress, 0); + instance.handleKeyDown(mockEnterKeyPress); expect(mockDispatch).toHaveBeenCalledTimes(2); }); @@ -298,6 +300,12 @@ describe('TabBar', () => { }); expect(result).toBe(`Live Chat (myEndpoint)`); + result = instance.getTabLabel({ + contentType: CONTENT_TYPE_MARKDOWN, + meta: { label: 'Hello' }, + }); + expect(result).toBe('Hello'); + result = instance.getTabLabel({}); expect(result).toBe(''); }); diff --git a/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.tsx b/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.tsx index 30f1c1b55..057f29c72 100644 --- a/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.tsx +++ b/packages/app/client/src/ui/shell/mdi/tabBar/tabBar.tsx @@ -33,7 +33,7 @@ import { BotConfigWithPath } from '@bfemulator/sdk-shared'; import * as React from 'react'; -import { DragEvent } from 'react'; +import { DragEvent, MouseEvent } from 'react'; import * as Constants from '../../../../constants'; import { @@ -155,7 +155,7 @@ export class TabBar extends React.Component { if (presentationEnabled) { widgets.push( - ); @@ -178,9 +178,10 @@ export class TabBar extends React.Component { return (
this.handleTabClick(index)} - onKeyDown={ev => this.handleKeyDown(ev, index)} + onClick={this.handleTabClick} + onKeyDown={this.handleKeyDown} ref={this.setRef} role="presentation" > @@ -196,15 +197,19 @@ export class TabBar extends React.Component { }); } - private handleTabClick = (tabIndex: number) => { - this.props.setActiveTab(this.props.tabOrder[tabIndex]); + private handleTabClick = (event: MouseEvent) => { + const { currentTarget } = event; + const { index } = currentTarget.dataset; + this.props.setActiveTab(this.props.tabOrder[index]); }; - private handleKeyDown = (event: React.KeyboardEvent, tabIndex: number): void => { + private handleKeyDown = (event: React.KeyboardEvent): void => { + const { currentTarget } = event; + const { index } = currentTarget.dataset; let { key = '' } = event; key = key.toLowerCase(); if (key === ' ' || key === 'enter') { - this.handleTabClick(tabIndex); + this.props.setActiveTab(this.props.tabOrder[index]); } };