diff --git a/build/gulp/tasks/test-unit.ts b/build/gulp/tasks/test-unit.ts new file mode 100644 index 0000000000..bfc2e28c3c --- /dev/null +++ b/build/gulp/tasks/test-unit.ts @@ -0,0 +1,41 @@ +import { parallel, series, task } from 'gulp' +import { argv } from 'yargs' + +import sh from '../sh' + +// ---------------------------------------- +// Jest +// ---------------------------------------- +const jest = ({ watch = false } = {}) => cb => { + process.env.NODE_ENV = 'test' + + // in watch mode jest never exits + // let the gulp task complete to prevent blocking subsequent tasks + const command = [ + 'jest --coverage', + watch && '--watchAll', + argv.runInBand && '--runInBand', + argv.maxWorkers && `--maxWorkers=${argv.maxWorkers}`, + ] + .filter(Boolean) + .join(' ') + + return sh(command) +} + +task('test:jest:pre', () => sh('yarn satisfied')) + +task( + 'test:jest:setup', + series('test:jest:pre', parallel('build:docs:docgen', 'build:docs:component-menu-behaviors')), +) + +task('test:jest', jest()) +task('test:jest:watch', jest({ watch: true })) + +// ---------------------------------------- +// Tests +// ---------------------------------------- + +task('test', series('test:jest:setup', 'test:jest')) +task('test:watch', series('test:jest:setup', parallel('test:jest:watch', 'watch:docs'))) diff --git a/gulpfile.ts b/gulpfile.ts index fb120d5dbb..ba23bc51e9 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -12,6 +12,7 @@ require('./build/gulp/tasks/docs') require('./build/gulp/tasks/generate') require('./build/gulp/tasks/screener') require('./build/gulp/tasks/git') +require('./build/gulp/tasks/test-unit') require('./build/gulp/tasks/test-projects') // global tasks diff --git a/package.json b/package.json index 470630315e..6bb1aa099a 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,11 @@ "prestart": "yarn satisfied --fix yarn", "start": "gulp --series dll docs", "satisfied": "satisfied --skip-invalid --ignore \"^typescript$\"", - "pretest": "yarn satisfied && gulp build:docs:docgen && gulp build:docs:component-menu-behaviors", - "test": "cross-env NODE_ENV=test jest --coverage", + "pretest": "yarn satisfied && gulp --series build:docs:docgen build:docs:component-menu-behaviors", + "test": "gulp test", "test:visual": "gulp screener", "test:projects:cra-ts": "gulp test:projects:cra-ts", - "test:watch": "yarn test --watchAll", + "test:watch": "gulp test:watch", "generate:component": "gulp generate:component" }, "lint-staged": { diff --git a/src/components/Button/ButtonGroup.tsx b/src/components/Button/ButtonGroup.tsx index 5103508e90..c1c378c24b 100644 --- a/src/components/Button/ButtonGroup.tsx +++ b/src/components/Button/ButtonGroup.tsx @@ -27,7 +27,7 @@ export interface ButtonGroupProps { } /** - * A button group. + * A button group presents multiple related actions. */ class ButtonGroup extends UIComponent, any> { public static displayName = 'ButtonGroup' diff --git a/src/components/Chat/ChatItem.tsx b/src/components/Chat/ChatItem.tsx index 09501657fe..5086242514 100644 --- a/src/components/Chat/ChatItem.tsx +++ b/src/components/Chat/ChatItem.tsx @@ -22,6 +22,9 @@ export interface ChatItemProps { variables?: ComponentVariablesInput } +/** + * A chat item represents a single event in a chat. + */ class ChatItem extends UIComponent, any> { static className = 'ui-chat__item' diff --git a/src/components/Chat/ChatMessage.tsx b/src/components/Chat/ChatMessage.tsx index 8ac2f35d6e..acead5f639 100644 --- a/src/components/Chat/ChatMessage.tsx +++ b/src/components/Chat/ChatMessage.tsx @@ -46,6 +46,9 @@ export interface ChatMessageProps { variables?: ComponentVariablesInput } +/** + * A chat message represents a single statement communicated to a user. + */ class ChatMessage extends UIComponent, any> { static className = 'ui-chat__message' diff --git a/src/components/Label/Label.tsx b/src/components/Label/Label.tsx index ac924a3a49..0984402b6b 100644 --- a/src/components/Label/Label.tsx +++ b/src/components/Label/Label.tsx @@ -39,7 +39,7 @@ export interface LabelProps { } /** - * A label displays content classification + * A label is used to classify content. */ class Label extends UIComponent, any> { static displayName = 'Label' diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 60f427f78b..436468140c 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -37,6 +37,9 @@ export interface LayoutProps { variables?: ComponentVariablesInput } +/** + * A layout is a utility for arranging the content of a component. + */ class Layout extends UIComponent, any> { static className = 'ui-layout' diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx index 99e3ccbaff..3f8b6bec3c 100644 --- a/src/components/List/List.tsx +++ b/src/components/List/List.tsx @@ -34,6 +34,9 @@ export interface ListProps extends FocusContainerProps { variables?: ComponentVariablesInput } +/** + * A list displays a group of related content. + */ class List extends UIComponent, FocusContainerState> { static displayName = 'List' diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx index 3e5710bc3e..930091db3f 100644 --- a/src/components/List/ListItem.tsx +++ b/src/components/List/ListItem.tsx @@ -33,6 +33,9 @@ export interface ListItemState { isHovering: boolean } +/** + * A list item contains a single piece of content within a list. + */ class ListItem extends UIComponent, ListItemState> { static create: Function diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index a3c41f1be6..6b53a8619f 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -39,6 +39,9 @@ export interface MenuProps { variables?: ComponentVariablesInput } +/** + * A menu displays grouped navigation actions. + */ class Menu extends AutoControlledComponent, any> { static displayName = 'Menu' diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index abbfd9169b..110038336d 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -44,6 +44,9 @@ export interface MenuItemState { [IsFromKeyboard.propertyName]: boolean } +/** + * A menu item is an actionable navigation item within a menu. + */ class MenuItem extends UIComponent, MenuItemState> { static displayName = 'MenuItem' diff --git a/src/components/RadioGroup/RadioGroupItem.tsx b/src/components/RadioGroup/RadioGroupItem.tsx index 57319219ec..1a28e9c659 100644 --- a/src/components/RadioGroup/RadioGroupItem.tsx +++ b/src/components/RadioGroup/RadioGroupItem.tsx @@ -45,6 +45,7 @@ export interface RadioGroupItemState { } /** + * A single radio within a radio group. * @accessibility * Radio items need to be grouped in RadioGroup component to correctly handle accessibility. */ diff --git a/src/components/Segment/Segment.tsx b/src/components/Segment/Segment.tsx index b658af0b15..bb6d4309b4 100644 --- a/src/components/Segment/Segment.tsx +++ b/src/components/Segment/Segment.tsx @@ -12,6 +12,9 @@ export interface SegmentProps { variables?: ComponentVariablesInput } +/** + * A segment is used to create a grouping of related content. + */ class Segment extends UIComponent, any> { static className = 'ui-segment' diff --git a/test/specs/commonTests/isConformant.tsx b/test/specs/commonTests/isConformant.tsx index 5829470a44..a9f97c63c9 100644 --- a/test/specs/commonTests/isConformant.tsx +++ b/test/specs/commonTests/isConformant.tsx @@ -104,6 +104,27 @@ export default (Component, options: Conformant = {}) => { return null } + // ---------------------------------------- + // Docblock description + // ---------------------------------------- + const hasDocblockDescription = info.docblock.description.join('').trim().length > 0 + + test('has a docblock description', () => { + expect(hasDocblockDescription).toEqual(true) + }) + + if (hasDocblockDescription) { + const minWords = 5 + const maxWords = 25 + test(`docblock description is long enough to be meaningful (>${minWords} words)`, () => { + expect(_.words(info.docblock.description).length).toBeGreaterThan(minWords) + }) + + test(`docblock description is short enough to be quickly understood (<${maxWords} words)`, () => { + expect(_.words(info.docblock.description).length).toBeLessThan(maxWords) + }) + } + // ---------------------------------------- // Class and file name // ---------------------------------------- @@ -303,7 +324,7 @@ export default (Component, options: Conformant = {}) => { 'forgot to use `getUnhandledProps` util to spread the `rest` props.', ) } - const customHandler = eventTarget.prop([listenerName]) + const customHandler = eventTarget.prop(listenerName) if (customHandler) { customHandler(eventShape) diff --git a/test/specs/components/RadioGroup/RadioGroup-test.tsx b/test/specs/components/RadioGroup/RadioGroup-test.tsx index ccb2f91825..e8e1d97839 100644 --- a/test/specs/components/RadioGroup/RadioGroup-test.tsx +++ b/test/specs/components/RadioGroup/RadioGroup-test.tsx @@ -42,9 +42,7 @@ const getShorthandItems = (props?: { disabledItem?: number }) => [ ] describe('RadioGroup', () => { - describe('isConformant', () => { - isConformant(RadioGroup) - }) + isConformant(RadioGroup) describe('accessibility', () => { handlesAccessibility(RadioGroup, { diff --git a/test/specs/components/RadioGroup/RadioGroupItem-test.ts b/test/specs/components/RadioGroup/RadioGroupItem-test.ts index be45f6d63c..bb2ee4a6f5 100644 --- a/test/specs/components/RadioGroup/RadioGroupItem-test.ts +++ b/test/specs/components/RadioGroup/RadioGroupItem-test.ts @@ -3,9 +3,7 @@ import { isConformant, handlesAccessibility } from 'test/specs/commonTests' import RadioGroupItem from 'src/components/RadioGroup/RadioGroupItem' describe('RadioGroupItem', () => { - describe('isConformant', () => { - isConformant(RadioGroupItem) - }) + isConformant(RadioGroupItem) describe('accessibility', () => { handlesAccessibility(RadioGroupItem, {