Skip to content

Commit

Permalink
feat: UI implementation for Linting and Validation (microsoft#1518)
Browse files Browse the repository at this point in the history
* lint ui

* update some ui

* update some function

* update some naming and clean some component

* hoist the filter state

* convert to es6 module imports of office ui

* fix some conflict

* fix linting

* fix e2e test

* add format-message

* add e2e test and unit test
  • Loading branch information
lei9444 authored and cwhitten committed Nov 18, 2019
1 parent f06110a commit 4831b79
Show file tree
Hide file tree
Showing 14 changed files with 420 additions and 59 deletions.
25 changes: 25 additions & 0 deletions Composer/cypress/integration/NotificationPage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// <reference types="Cypress" />

context('check notifications page', () => {
beforeEach(() => {
cy.visit(Cypress.env('COMPOSER_URL'));
cy.createBot('TodoSample');
});

it('can show lg syntax error ', () => {
cy.visitPage("Bot Responses");
// left nav tree
cy.contains('TodoSample.Main');
cy.contains('All');

cy.get('.toggleEditMode button').click();
cy.get('textarea').type('test lg syntax error');

cy.visitPage("Notifications");

cy.get('[data-testid="notifications-table-view"]').within(() => {
cy.getByText('common.lg').should('exist');
});

});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import * as React from 'react';
import { fireEvent, render } from 'react-testing-library';

import { NotificationHeader } from '../../src/pages/notifications/NotificationHeader';

describe('<NotificationHeader/>', () => {
const items = ['test1', 'test2', 'test3'];
it('should render the NotificationHeader', () => {
const mockOnChange = jest.fn(() => null);
const { container } = render(<NotificationHeader items={items} onChange={mockOnChange} />);

expect(container).toHaveTextContent('Notifications');
expect(container).toHaveTextContent('All');
const dropdown = container.querySelector('[data-testid="notifications-dropdown"]');
fireEvent.click(dropdown);
const test = document.querySelector('.ms-Dropdown-callout');
expect(test).toHaveTextContent('test1');
expect(test).toHaveTextContent('test2');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import * as React from 'react';
import { render } from 'react-testing-library';

import { NotificationList } from '../../src/pages/notifications/NotificationList';

describe('<NotificationList/>', () => {
const items = [
{ type: 'Error', location: 'test1', message: 'error1' },
{ type: 'Warning', location: 'test2', message: 'error2' },
{ type: 'Error', location: 'test3', message: 'error3' },
];
it('should render the NotificationList', () => {
const { container } = render(<NotificationList items={items} />);

expect(container).toHaveTextContent('test1');
expect(container).toHaveTextContent('test2');
expect(container).toHaveTextContent('test3');
});
});
46 changes: 20 additions & 26 deletions Composer/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,56 +33,57 @@ const topLinks = (botLoaded: boolean) => {
to: '/home',
iconName: 'Home',
labelName: formatMessage('Home'),
activeIfUrlContains: 'home',
exact: true,
disabled: false,
},
{
to: '/dialogs/Main',
iconName: 'SplitObject',
labelName: formatMessage('Design Flow'),
activeIfUrlContains: 'dialogs',
exact: false,
underTest: !botLoaded,
disabled: !botLoaded,
},
{
to: '/test-conversation',
iconName: 'WaitListConfirm',
labelName: formatMessage('Test Conversation'),
activeIfUrlContains: '',
exact: false,
underTest: true, // will delete
disabled: true, // will delete
},
{
to: 'language-generation/',
iconName: 'Robot',
labelName: formatMessage('Bot Responses'),
activeIfUrlContains: 'language-generation',
exact: false,
underTest: !botLoaded,
disabled: !botLoaded,
},
{
to: 'language-understanding/',
iconName: 'People',
labelName: formatMessage('User Input'),
activeIfUrlContains: 'language-understanding',
exact: false,
underTest: !botLoaded,
disabled: !botLoaded,
},
{
to: '/evaluate-performance',
iconName: 'Chart',
labelName: formatMessage('Evaluate performance'),
activeIfUrlContains: '',
exact: false,
underTest: true, // will delete
disabled: true,
},
{
to: '/notifications',
iconName: 'Warning',
labelName: formatMessage('Notifications'),
exact: true,
disabled: !botLoaded,
},
{
to: '/setting/',
iconName: 'Settings',
labelName: formatMessage('Settings'),
activeIfUrlContains: 'setting',
exact: false,
underTest: !botLoaded,
disabled: !botLoaded,
},
];

Expand All @@ -98,16 +99,15 @@ const bottomLinks = [
to: '/help',
iconName: 'unknown',
labelName: formatMessage('Info'),
activeIfUrlContains: '/help',
exact: false,
underTest: true, // will delete
exact: true,
disabled: true,
},
{
to: '/about',
iconName: 'info',
labelName: formatMessage('About'),
activeIfUrlContains: '/about',
exact: false,
exact: true,
disabled: false,
},
];

Expand Down Expand Up @@ -143,11 +143,8 @@ export const App: React.FC = () => {
to={mapNavItemTo(link.to)}
iconName={link.iconName}
labelName={link.labelName}
labelHide={!sideBarExpand}
index={index}
exact={link.exact}
targetUrl={link.activeIfUrlContains}
underTest={link.underTest}
disabled={link.disabled}
/>
);
})}
Expand All @@ -161,11 +158,8 @@ export const App: React.FC = () => {
to={mapNavItemTo(link.to)}
iconName={link.iconName}
labelName={link.labelName}
labelHide={!sideBarExpand}
index={index}
exact={link.exact}
targetUrl={link.activeIfUrlContains}
underTest={link.underTest}
disabled={link.disabled}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,51 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { useCallback, useContext, useState } from 'react';
import { Link } from '@reach/router';
import { PropTypes } from 'prop-types';
import { Link, LinkGetProps } from '@reach/router';
import { CommandBarButton } from 'office-ui-fabric-react/lib/Button';
import { FocusZone } from 'office-ui-fabric-react/lib/FocusZone';

import { StoreContext } from '../../store';

import { link, outer, commandBarButton } from './styles';

export const NavItem = props => {
/**
* @param to The string URI to link to. Supports relative and absolute URIs.
* @param exact The uri is exactly the same as the anchor’s href.
* @param iconName The link's icon.
* @param labelName The link's text.
* @param disabled If true, the Link will be unavailable.
*/
export interface INavItemProps {
to: string;
exact: boolean;
iconName: string;
labelName: string;
disabled: boolean;
}

export const NavItem: React.FC<INavItemProps> = props => {
const {
actions: { onboardingAddCoachMarkRef },
} = useContext(StoreContext);

const { to, exact, iconName, labelName, targetUrl, underTest } = props;
const { to, exact, iconName, labelName, disabled } = props;
const [active, setActive] = useState(false);

const addRef = useCallback(ref => onboardingAddCoachMarkRef({ [`nav${labelName.replace(' ', '')}`]: ref }), []);

const isPartial = (targetUrl, currentUrl) => {
const urlPaths = currentUrl.split('/');
return urlPaths.indexOf(targetUrl) !== -1;
};

return (
<FocusZone allowFocusRoot={true} disabled={underTest}>
<FocusZone allowFocusRoot={true} disabled={disabled}>
<Link
to={to}
css={link(active, underTest)}
getProps={({ isCurrent, location }) => {
const isActive = exact ? isCurrent : isPartial(targetUrl, location.pathname);
css={link(active, disabled)}
getProps={(props: LinkGetProps) => {
const isActive = exact ? props.isCurrent : props.isPartiallyCurrent;
setActive(isActive);
return {};
}}
data-testid={'LeftNav-CommandBarButton' + labelName}
disabled={underTest}
aria-disabled={underTest}
aria-disabled={disabled}
aria-label={labelName}
ref={addRef}
>
Expand All @@ -50,21 +59,11 @@ export const NavItem = props => {
}}
text={labelName}
styles={commandBarButton(active)}
disabled={underTest}
disabled={disabled}
ariaHidden
/>
</div>
</Link>
</FocusZone>
);
};
NavItem.propTypes = {
to: PropTypes.string,
iconName: PropTypes.string,
labelName: PropTypes.string,
exact: PropTypes.bool,
labelHide: PropTypes.bool,
index: PropTypes.number,
targetUrl: PropTypes.string,
underTest: PropTypes.bool,
};
6 changes: 3 additions & 3 deletions Composer/packages/client/src/components/NavItem/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { FontSizes } from '@uifabric/fluent-theme';
import { NeutralColors, CommunicationColors } from '@uifabric/fluent-theme';
import { IButtonStyles } from 'office-ui-fabric-react/lib/Button';

export const link = (active, underTest) => css`
export const link = (active, disabled) => css`
display: block;
text-decoration: none;
color: #4f4f4f;
position: relative;
${underTest && `pointer-events: none;`}
${!underTest &&
${disabled && `pointer-events: none;`}
${!disabled &&
`&::after {
content: '';
position: absolute;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import formatMessage from 'format-message';
import { useMemo } from 'react';

import { notificationHeader, notificationHeaderText, dropdownStyles } from './styles';

const createOptions = (items: string[]): IDropdownOption[] => {
const defaultOptions: IDropdownOption[] = [
{ key: formatMessage('Show All Locations'), text: formatMessage('All'), data: '', isSelected: true },
];
items.forEach(item => {
return defaultOptions.push({ key: item, text: item, data: item });
});
return defaultOptions;
};

export interface INotificationHeader {
onChange: (text: string) => void;
items: string[];
}

export const NotificationHeader: React.FC<INotificationHeader> = props => {
const { onChange, items } = props;
const options = useMemo(() => {
return createOptions(items);
}, [items]);

return (
<div css={notificationHeader}>
<div css={notificationHeaderText}>{formatMessage('Notifications')}</div>
<Dropdown
onChange={(event, option) => {
if (option) onChange(option.data);
}}
options={options}
styles={dropdownStyles}
data-testid="notifications-dropdown"
/>
</div>
);
};
Loading

0 comments on commit 4831b79

Please sign in to comment.