diff --git a/.eslintrc b/.eslintrc
index 9a37b08057..12880dff98 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -25,6 +25,7 @@
"semi": [2, "never"],
"jsx-a11y/alt-text": 1,
"jsx-a11y/label-has-for": 1,
+ "jsx-a11y/no-static-element-interactions": 1,
"jsx-a11y/role-has-required-aria-props": 1,
"import/no-dynamic-require": 0,
"import/no-extraneous-dependencies": 0,
diff --git a/docs/src/components/ComponentDoc/ComponentDocLinks.js b/docs/src/components/ComponentDoc/ComponentDocLinks.js
index 8188870a11..589ea6743c 100644
--- a/docs/src/components/ComponentDoc/ComponentDocLinks.js
+++ b/docs/src/components/ComponentDoc/ComponentDocLinks.js
@@ -33,7 +33,7 @@ export default class ComponentDocLinks extends PureComponent {
-
+
{repoPath}
@@ -43,7 +43,7 @@ export default class ComponentDocLinks extends PureComponent {
{suiLink && (
+
Semantic UI {displayName} Docs
}
diff --git a/docs/src/components/IconSearch/IconSearch.js b/docs/src/components/IconSearch/IconSearch.js
index 9ab0b8e794..d97cd05dd5 100644
--- a/docs/src/components/IconSearch/IconSearch.js
+++ b/docs/src/components/IconSearch/IconSearch.js
@@ -115,7 +115,6 @@ export default class IconSearch extends Component {
mouseEnterDelay={1000}
inverted
closeOnTriggerClick={false}
- closeOnRootNodeClick={false}
closeOnDocumentClick={false}
style={{ width: '8em', textAlign: 'center' }}
size='mini'
diff --git a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleForm.js b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleForm.js
index caba3ea53c..a35d34e237 100644
--- a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleForm.js
+++ b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleForm.js
@@ -3,10 +3,10 @@ import { Accordion, Button, Form, Segment } from 'semantic-ui-react'
const panels = [
{
+ key: 'details',
title: 'Optional Details',
content: {
as: Form.Input,
- key: 'content',
label: 'Maiden Name',
placeholder: 'Maiden Name',
},
diff --git a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleNested.js b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleNested.js
index b94edb868a..34d96046a3 100644
--- a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleNested.js
+++ b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleNested.js
@@ -2,8 +2,8 @@ import React from 'react'
import { Accordion } from 'semantic-ui-react'
const level1Panels = [
- { title: 'Level 1A', content: 'Level 1A Contents' },
- { title: 'Level 1B', content: 'Level 1B Contents' },
+ { key: 'panel-1a', title: 'Level 1A', content: 'Level 1A Contents' },
+ { key: 'panel-ba', title: 'Level 1B', content: 'Level 1B Contents' },
]
const Level1Content = (
@@ -14,8 +14,8 @@ const Level1Content = (
)
const level2Panels = [
- { title: 'Level 2A', content: 'Level 2A Contents' },
- { title: 'Level 2B', content: 'Level 2B Contents' },
+ { key: 'panel-2a', title: 'Level 2A', content: 'Level 2A Contents' },
+ { key: 'panel-2b', title: 'Level 2B', content: 'Level 2B Contents' },
]
const Level2Content = (
@@ -26,8 +26,8 @@ const Level2Content = (
)
const rootPanels = [
- { title: 'Level 1', content: { content: Level1Content, key: 'content-1' } },
- { title: 'Level 2', content: { content: Level2Content, key: 'content-2' } },
+ { key: 'panel-1', title: 'Level 1', content: { content: Level1Content } },
+ { key: 'panel-2', title: 'Level 2', content: { content: Level2Content } },
]
const AccordionExampleNested = () =>
diff --git a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleShorthand.js b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleShorthand.js
index 2807c99193..6b788ca2fc 100644
--- a/docs/src/examples/modules/Accordion/Advanced/AccordionExampleShorthand.js
+++ b/docs/src/examples/modules/Accordion/Advanced/AccordionExampleShorthand.js
@@ -4,13 +4,12 @@ import React from 'react'
import { Accordion, Label, Message } from 'semantic-ui-react'
const panels = _.times(3, i => ({
+ key: `panel-${i}`,
title: {
content: ,
- key: `title-${i}`,
},
content: {
content: ,
- key: `content-${i}`,
},
}))
diff --git a/docs/src/examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js b/docs/src/examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js
index b0c3dbf2bf..d511ccfe23 100644
--- a/docs/src/examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js
+++ b/docs/src/examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js
@@ -3,6 +3,7 @@ import { Accordion } from 'semantic-ui-react'
const panels = [
{
+ key: 'what-is-dog',
title: 'What is a dog?',
content: [
'A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome',
@@ -10,6 +11,7 @@ const panels = [
].join(' '),
},
{
+ key: 'kinds-of-dogs',
title: 'What kinds of dogs are there?',
content: [
'There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of dog',
@@ -17,6 +19,7 @@ const panels = [
].join(' '),
},
{
+ key: 'acquire-dog',
title: 'How do you acquire a dog?',
content: {
content: (
@@ -33,7 +36,6 @@ const panels = [
),
- key: 'content-dog',
},
},
]
diff --git a/docs/src/examples/modules/Accordion/Usage/AccordionExampleActiveIndex.js b/docs/src/examples/modules/Accordion/Usage/AccordionExampleActiveIndex.js
index 2b7c0ba7d3..74ca849501 100644
--- a/docs/src/examples/modules/Accordion/Usage/AccordionExampleActiveIndex.js
+++ b/docs/src/examples/modules/Accordion/Usage/AccordionExampleActiveIndex.js
@@ -3,7 +3,8 @@ import _ from 'lodash'
import React, { Component } from 'react'
import { Accordion, Segment } from 'semantic-ui-react'
-const panels = _.times(3, () => ({
+const panels = _.times(3, i => ({
+ key: `panel-${i}`,
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(),
}))
diff --git a/docs/src/examples/modules/Accordion/Usage/AccordionExampleExclusive.js b/docs/src/examples/modules/Accordion/Usage/AccordionExampleExclusive.js
index 0d699e3228..d73c9e9464 100644
--- a/docs/src/examples/modules/Accordion/Usage/AccordionExampleExclusive.js
+++ b/docs/src/examples/modules/Accordion/Usage/AccordionExampleExclusive.js
@@ -3,7 +3,8 @@ import _ from 'lodash'
import React from 'react'
import { Accordion } from 'semantic-ui-react'
-const panels = _.times(3, () => ({
+const panels = _.times(3, i => ({
+ key: `panel-${i}`,
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(),
}))
diff --git a/docs/src/examples/modules/Modal/Variations/ModalExampleCloseConfig.js b/docs/src/examples/modules/Modal/Variations/ModalExampleCloseConfig.js
index a0a9072563..41a2c9e965 100644
--- a/docs/src/examples/modules/Modal/Variations/ModalExampleCloseConfig.js
+++ b/docs/src/examples/modules/Modal/Variations/ModalExampleCloseConfig.js
@@ -4,14 +4,14 @@ import { Button, Modal } from 'semantic-ui-react'
class ModalExampleCloseConfig extends Component {
state = { open: false }
- closeConfigShow = (closeOnEscape, closeOnRootNodeClick) => () => {
- this.setState({ closeOnEscape, closeOnRootNodeClick, open: true })
+ closeConfigShow = (closeOnEscape, closeOnDimmerClick) => () => {
+ this.setState({ closeOnEscape, closeOnDimmerClick, open: true })
}
close = () => this.setState({ open: false })
render() {
- const { open, closeOnEscape, closeOnRootNodeClick } = this.state
+ const { open, closeOnEscape, closeOnDimmerClick } = this.state
return (
@@ -21,7 +21,8 @@ class ModalExampleCloseConfig extends Component {
Delete Your Account
diff --git a/index.d.ts b/index.d.ts
index fb1e86aa51..790d9d86a3 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -8,6 +8,10 @@ export {
PaginationItemProps,
} from './dist/commonjs/addons/Pagination/PaginationItem'
export { default as Portal, PortalProps } from './dist/commonjs/addons/Portal'
+export {
+ default as PortalInner,
+ PortalInnerProps,
+} from './dist/commonjs/addons/Portal/PortalInner'
export { default as Radio, RadioProps } from './dist/commonjs/addons/Radio'
export { default as Ref, RefProps } from './dist/commonjs/addons/Ref'
export {
@@ -212,12 +216,15 @@ export { default as Accordion, AccordionProps } from './dist/commonjs/modules/Ac
export {
default as AccordionAccordion,
AccordionAccordionProps,
- AccordionPanelProps,
} from './dist/commonjs/modules/Accordion/AccordionAccordion'
export {
default as AccordionContent,
AccordionContentProps,
} from './dist/commonjs/modules/Accordion/AccordionContent'
+export {
+ default as AccordionPanel,
+ AccordionPanelProps,
+} from './dist/commonjs/modules/Accordion/AccordionPanel'
export {
default as AccordionTitle,
AccordionTitleProps,
diff --git a/package.json b/package.json
index 4ab0daf9e9..c3f99dfec3 100644
--- a/package.json
+++ b/package.json
@@ -160,7 +160,7 @@
"webpack-hot-middleware": "^2.18.2"
},
"peerDependencies": {
- "react": ">=0.14.0 <= 16",
- "react-dom": ">=0.14.0 <= 16"
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0"
}
}
diff --git a/src/addons/Portal/Portal.d.ts b/src/addons/Portal/Portal.d.ts
index 6d83139e36..b6198338a0 100644
--- a/src/addons/Portal/Portal.d.ts
+++ b/src/addons/Portal/Portal.d.ts
@@ -1,4 +1,5 @@
import * as React from 'react'
+import { default as PortalInner } from './PortalInner'
export interface PortalProps {
[key: string]: any
@@ -6,9 +7,6 @@ export interface PortalProps {
/** Primary content. */
children?: React.ReactNode
- /** Additional classes. */
- className?: string
-
/** Controls whether or not the portal should close on a click outside. */
closeOnDocumentClick?: boolean
@@ -22,14 +20,6 @@ export interface PortalProps {
*/
closeOnPortalMouseLeave?: boolean
- /**
- * Controls whether or not the portal should close on a click on the portal background.
- * NOTE: This differs from closeOnDocumentClick:
- * - DocumentClick - any click not within the portal
- * - RootNodeClick - a click not within the portal but within the portal's wrapper
- */
- closeOnRootNodeClick?: boolean
-
/** Controls whether or not the portal should close on blur of the trigger. */
closeOnTriggerBlur?: boolean
@@ -98,13 +88,19 @@ export interface PortalProps {
/** Controls whether or not the portal should open when mousing over the trigger. */
openOnTriggerMouseEnter?: boolean
- /** Controls whether the portal should be prepended to the mountNode instead of appended. */
- prepend?: boolean
-
/** Element to be rendered in-place where the portal is defined. */
trigger?: React.ReactNode
+
+ /**
+ * Called when componentDidMount.
+ *
+ * @param {HTMLElement} node - Referred node.
+ */
+ triggerRef?: (node: HTMLElement) => void
}
-declare const Portal: React.ComponentClass
+declare class Portal extends React.Component {
+ static Inner: typeof PortalInner
+}
export default Portal
diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js
index ec79ba3c98..17748f926e 100644
--- a/src/addons/Portal/Portal.js
+++ b/src/addons/Portal/Portal.js
@@ -1,17 +1,16 @@
import keyboardKey from 'keyboard-key'
import _ from 'lodash'
import PropTypes from 'prop-types'
-import React, { Children, cloneElement } from 'react'
-import ReactDOM from 'react-dom'
+import React, { cloneElement } from 'react'
import {
AutoControlledComponent as Component,
doesNodeContainClick,
eventStack,
- isBrowser,
makeDebugger,
} from '../../lib'
import Ref from '../Ref'
+import PortalInner from './PortalInner'
const debug = makeDebugger('portal')
@@ -27,9 +26,6 @@ class Portal extends Component {
/** Primary content. */
children: PropTypes.node.isRequired,
- /** Additional classes. */
- className: PropTypes.string,
-
/** Controls whether or not the portal should close when the document is clicked. */
closeOnDocumentClick: PropTypes.bool,
@@ -43,14 +39,6 @@ class Portal extends Component {
*/
closeOnPortalMouseLeave: PropTypes.bool,
- /**
- * Controls whether or not the portal should close on a click on the portal background.
- * NOTE: This differs from closeOnDocumentClick:
- * - DocumentClick - any click not within the portal
- * - RootNodeClick - a click not within the portal but within the portal's wrapper
- */
- closeOnRootNodeClick: PropTypes.bool,
-
/** Controls whether or not the portal should close on blur of the trigger. */
closeOnTriggerBlur: PropTypes.bool,
@@ -84,7 +72,7 @@ class Portal extends Component {
onClose: PropTypes.func,
/**
- * Called when the portal is mounted on the DOM
+ * Called when the portal is mounted on the DOM.
*
* @param {null}
* @param {object} data - All props.
@@ -100,7 +88,7 @@ class Portal extends Component {
onOpen: PropTypes.func,
/**
- * Called when the portal is unmounted from the DOM
+ * Called when the portal is unmounted from the DOM.
*
* @param {null}
* @param {object} data - All props.
@@ -119,11 +107,15 @@ class Portal extends Component {
/** Controls whether or not the portal should open when mousing over the trigger. */
openOnTriggerMouseEnter: PropTypes.bool,
- /** Controls whether the portal should be prepended to the mountNode instead of appended. */
- prepend: PropTypes.bool,
-
/** Element to be rendered in-place where the portal is defined. */
trigger: PropTypes.node,
+
+ /**
+ * Called with a ref to the trigger node.
+ *
+ * @param {HTMLElement} node - Referred node.
+ */
+ triggerRef: PropTypes.func,
}
static defaultProps = {
@@ -135,29 +127,9 @@ class Portal extends Component {
static autoControlledProps = ['open']
- componentDidMount() {
- debug('componentDidMount()')
- this.renderPortal()
- }
-
- componentDidUpdate(prevProps, prevState) {
- debug('componentDidUpdate()')
- // NOTE: Ideally the portal rendering would happen in the render() function
- // but React gives a warning about not being pure and suggests doing it
- // within this method.
-
- // If the portal is open, render (or re-render) the portal and child.
- this.renderPortal()
-
- if (prevState.open && !this.state.open) {
- debug('portal closed')
- this.unmountPortal()
- }
- }
+ static Inner = PortalInner
componentWillUnmount() {
- this.unmountPortal()
-
// Clean up timers
clearTimeout(this.mouseEnterTimer)
clearTimeout(this.mouseLeaveTimer)
@@ -168,10 +140,9 @@ class Portal extends Component {
// ----------------------------------------
handleDocumentClick = (e) => {
- const { closeOnDocumentClick, closeOnRootNodeClick } = this.props
+ const { closeOnDocumentClick } = this.props
if (
- !this.rootNode || // not mounted
!this.portalNode || // no portal
doesNodeContainClick(this.triggerNode, e) || // event happened in trigger (delegate to trigger handlers)
doesNodeContainClick(this.portalNode, e) // event happened in the portal
@@ -179,14 +150,8 @@ class Portal extends Component {
return
} // ignore the click
- const didClickInRootNode = doesNodeContainClick(this.rootNode, e)
-
- if (
- (closeOnDocumentClick && !didClickInRootNode) ||
- (closeOnRootNodeClick && didClickInRootNode)
- ) {
+ if (closeOnDocumentClick) {
debug('handleDocumentClick()')
-
this.close(e)
}
}
@@ -196,7 +161,6 @@ class Portal extends Component {
if (keyboardKey.getCode(e) !== keyboardKey.Escape) return
debug('handleEscape()')
-
this.close(e)
}
@@ -337,116 +301,66 @@ class Portal extends Component {
return setTimeout(() => this.close(eventClone), delay || 0)
}
- renderPortal() {
- if (!this.state.open) return
- debug('renderPortal()')
-
- const { children, className, eventPool } = this.props
-
- this.mountPortal()
-
- // Server side rendering
- if (!isBrowser()) return null
-
- this.rootNode.className = className || ''
-
- // when re-rendering, first remove listeners before re-adding them to the new node
- if (this.portalNode) {
- eventStack.unsub('mouseleave', this.handlePortalMouseLeave, {
- pool: eventPool,
- target: this.portalNode,
- })
- eventStack.unsub('mouseenter', this.handlePortalMouseEnter, {
- pool: eventPool,
- target: this.portalNode,
- })
- }
-
- ReactDOM.unstable_renderSubtreeIntoContainer(this, Children.only(children), this.rootNode, () =>
- this.attachRenderSubTreeSubscribers(eventPool),
- )
- }
-
- attachRenderSubTreeSubscribers = (eventPool) => {
- // Prevent race condition bug
- // https://github.com/Semantic-Org/Semantic-UI-React/issues/2401
- if (!this.rootNode) return null
-
- this.portalNode = this.rootNode.firstElementChild
-
- eventStack.sub('mouseleave', this.handlePortalMouseLeave, {
- pool: eventPool,
- target: this.portalNode,
- })
- eventStack.sub('mouseenter', this.handlePortalMouseEnter, {
- pool: eventPool,
- target: this.portalNode,
- })
- }
-
- mountPortal = () => {
- if (!isBrowser() || this.rootNode) return
-
+ handleMount = (e, { node: target }) => {
debug('mountPortal()')
+ const { eventPool } = this.props
- const { eventPool, mountNode = isBrowser() ? document.body : null, prepend } = this.props
-
- this.rootNode = document.createElement('div')
-
- if (prepend) {
- mountNode.insertBefore(this.rootNode, mountNode.firstElementChild)
- } else {
- mountNode.appendChild(this.rootNode)
- }
+ this.portalNode = target
+ eventStack.sub('mouseleave', this.handlePortalMouseLeave, { pool: eventPool, target })
+ eventStack.sub('mouseenter', this.handlePortalMouseEnter, { pool: eventPool, target })
eventStack.sub('click', this.handleDocumentClick, { pool: eventPool })
eventStack.sub('keydown', this.handleEscape, { pool: eventPool })
+
_.invoke(this.props, 'onMount', null, this.props)
}
- unmountPortal = () => {
- if (!isBrowser() || !this.rootNode) return
-
+ handleUnmount = (e, { node: target }) => {
debug('unmountPortal()')
const { eventPool } = this.props
- ReactDOM.unmountComponentAtNode(this.rootNode)
- this.rootNode.parentNode.removeChild(this.rootNode)
-
- eventStack.unsub('mouseleave', this.handlePortalMouseLeave, {
- pool: eventPool,
- target: this.portalNode,
- })
- eventStack.unsub('mouseenter', this.handlePortalMouseEnter, {
- pool: eventPool,
- target: this.portalNode,
- })
-
- this.rootNode = null
this.portalNode = null
+ eventStack.unsub('mouseleave', this.handlePortalMouseLeave, { pool: eventPool, target })
+ eventStack.unsub('mouseenter', this.handlePortalMouseEnter, { pool: eventPool, target })
eventStack.unsub('click', this.handleDocumentClick, { pool: eventPool })
eventStack.unsub('keydown', this.handleEscape, { pool: eventPool })
+
_.invoke(this.props, 'onUnmount', null, this.props)
}
- handleRef = c => (this.triggerNode = c)
+ handleTriggerRef = (c) => {
+ this.triggerNode = c
+ _.invoke(this.props, 'triggerRef', c)
+ }
render() {
- const { trigger } = this.props
-
- if (!trigger) return null
- return (
- [
- {cloneElement(trigger, {
- onBlur: this.handleTriggerBlur,
- onClick: this.handleTriggerClick,
- onFocus: this.handleTriggerFocus,
- onMouseLeave: this.handleTriggerMouseLeave,
- onMouseEnter: this.handleTriggerMouseEnter,
- })}
- ]
- )
+ const { children, mountNode, trigger } = this.props
+ const { open } = this.state
+
+ return [
+ open ? (
+
+ {children}
+
+ ) : null,
+ trigger ? (
+ [
+ {cloneElement(trigger, {
+ onBlur: this.handleTriggerBlur,
+ onClick: this.handleTriggerClick,
+ onFocus: this.handleTriggerFocus,
+ onMouseLeave: this.handleTriggerMouseLeave,
+ onMouseEnter: this.handleTriggerMouseEnter,
+ })}
+ ]
+ ) : null,
+ ]
}
}
diff --git a/src/addons/Portal/PortalInner.d.ts b/src/addons/Portal/PortalInner.d.ts
new file mode 100644
index 0000000000..4d3e3e5ed9
--- /dev/null
+++ b/src/addons/Portal/PortalInner.d.ts
@@ -0,0 +1,31 @@
+import * as React from 'react'
+
+export interface PortalInnerProps {
+ [key: string]: any
+
+ /** Primary content. */
+ children: React.ReactNode
+
+ /** The node where the portal should mount. */
+ mountNode?: any
+
+ /**
+ * Called when the PortalInner is mounted on the DOM.
+ *
+ * @param {null}
+ * @param {object} data - All props.
+ */
+ onMount?: (nothing: null, data: PortalInnerProps) => void
+
+ /**
+ * Called when the PortalInner is unmounted from the DOM.
+ *
+ * @param {null}
+ * @param {object} data - All props.
+ */
+ onUnmount?: (nothing: null, data: PortalInnerProps) => void
+}
+
+declare class PortalInner extends React.Component {}
+
+export default PortalInner
diff --git a/src/addons/Portal/PortalInner.js b/src/addons/Portal/PortalInner.js
new file mode 100644
index 0000000000..9bc295b433
--- /dev/null
+++ b/src/addons/Portal/PortalInner.js
@@ -0,0 +1,54 @@
+import _ from 'lodash'
+import PropTypes from 'prop-types'
+import React, { Component } from 'react'
+import { createPortal } from 'react-dom'
+
+import { isBrowser } from '../../lib'
+import Ref from '../Ref'
+
+/**
+ * An inner component that allows you to render children outside their parent.
+ */
+class PortalInner extends Component {
+ static propTypes = {
+ /** Primary content. */
+ children: PropTypes.node.isRequired,
+
+ /** The node where the portal should mount. */
+ mountNode: PropTypes.any,
+
+ /**
+ * Called when the portal is mounted on the DOM
+ *
+ * @param {null}
+ * @param {object} data - All props.
+ */
+ onMount: PropTypes.func,
+
+ /**
+ * Called when the portal is unmounted from the DOM
+ *
+ * @param {null}
+ * @param {object} data - All props.
+ */
+ onUnmount: PropTypes.func,
+ }
+
+ componentDidMount() {
+ _.invoke(this.props, 'onMount', null, { ...this.props, node: this.ref })
+ }
+
+ componentWillUnmount() {
+ _.invoke(this.props, 'onUnmount', null, { ...this.props, node: this.ref })
+ }
+
+ handleRef = c => (this.ref = c)
+
+ render() {
+ const { children, mountNode = isBrowser() ? document.body : null } = this.props
+
+ return createPortal([{children}], mountNode)
+ }
+}
+
+export default PortalInner
diff --git a/src/elements/Flag/Flag.d.ts b/src/elements/Flag/Flag.d.ts
index 4aa786b34e..ce7ea05051 100644
--- a/src/elements/Flag/Flag.d.ts
+++ b/src/elements/Flag/Flag.d.ts
@@ -506,6 +506,6 @@ export interface FlagProps {
name: FlagNameValues
}
-declare class Flag extends React.Component {}
+declare class Flag extends React.PureComponent {}
export default Flag
diff --git a/src/elements/Flag/Flag.js b/src/elements/Flag/Flag.js
index dac78047dc..5496d9e3a1 100644
--- a/src/elements/Flag/Flag.js
+++ b/src/elements/Flag/Flag.js
@@ -1,13 +1,12 @@
import cx from 'classnames'
import PropTypes from 'prop-types'
-import React, { Component } from 'react'
+import React, { PureComponent } from 'react'
import {
createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
- shallowEqual,
} from '../../lib'
export const names = [
@@ -507,7 +506,7 @@ export const names = [
/**
* A flag is is used to represent a political state.
*/
-class Flag extends Component {
+class Flag extends PureComponent {
static propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,
@@ -523,10 +522,6 @@ class Flag extends Component {
as: 'i',
}
- shouldComponentUpdate(nextProps) {
- return !shallowEqual(this.props, nextProps)
- }
-
render() {
const { className, name } = this.props
const classes = cx(name, 'flag', className)
diff --git a/src/elements/Icon/Icon.d.ts b/src/elements/Icon/Icon.d.ts
index 76010beee6..202f0db520 100644
--- a/src/elements/Icon/Icon.d.ts
+++ b/src/elements/Icon/Icon.d.ts
@@ -60,7 +60,7 @@ export interface IconProps {
'aria-label'?: string
}
-declare class Icon extends React.Component {
+declare class Icon extends React.PureComponent {
static Group: typeof IconGroup
}
diff --git a/src/elements/Icon/Icon.js b/src/elements/Icon/Icon.js
index 3010e74eaa..a6a52b8add 100644
--- a/src/elements/Icon/Icon.js
+++ b/src/elements/Icon/Icon.js
@@ -1,14 +1,13 @@
import cx from 'classnames'
import _ from 'lodash'
import PropTypes from 'prop-types'
-import React, { Component } from 'react'
+import React, { PureComponent } from 'react'
import {
createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
- shallowEqual,
SUI,
useKeyOnly,
useValueAndKey,
@@ -19,7 +18,7 @@ import IconGroup from './IconGroup'
* An icon is a glyph used to represent something else.
* @see Image
*/
-class Icon extends Component {
+class Icon extends PureComponent {
static propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,
@@ -79,10 +78,6 @@ class Icon extends Component {
static Group = IconGroup
- shouldComponentUpdate(nextProps) {
- return !shallowEqual(this.props, nextProps)
- }
-
getIconAriaOptions() {
const ariaOptions = {}
const { 'aria-label': ariaLabel, 'aria-hidden': ariaHidden } = this.props
diff --git a/src/index.js b/src/index.js
index 42997db4d2..70a29e4976 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,6 +4,7 @@ export MountNode from './addons/MountNode'
export Pagination from './addons/Pagination'
export PaginationItem from './addons/Pagination/PaginationItem'
export Portal from './addons/Portal'
+export PortalInner from './addons/Portal/PortalInner'
export Radio from './addons/Radio'
export Ref from './addons/Ref'
export Responsive from './addons/Responsive'
@@ -109,6 +110,7 @@ export StepTitle from './elements/Step/StepTitle'
export Accordion from './modules/Accordion/Accordion'
export AccordionAccordion from './modules/Accordion/AccordionAccordion'
export AccordionContent from './modules/Accordion/AccordionContent'
+export AccordionPanel from './modules/Accordion/AccordionPanel'
export AccordionTitle from './modules/Accordion/AccordionTitle'
export Checkbox from './modules/Checkbox'
diff --git a/src/modules/Accordion/Accordion.js b/src/modules/Accordion/Accordion.js
index 820d83ea08..645fb8d043 100644
--- a/src/modules/Accordion/Accordion.js
+++ b/src/modules/Accordion/Accordion.js
@@ -5,6 +5,7 @@ import React from 'react'
import { getUnhandledProps, useKeyOnly } from '../../lib'
import AccordionAccordion from './AccordionAccordion'
import AccordionContent from './AccordionContent'
+import AccordionPanel from './AccordionPanel'
import AccordionTitle from './AccordionTitle'
/**
@@ -41,6 +42,7 @@ Accordion.propTypes = {
Accordion.Accordion = AccordionAccordion
Accordion.Content = AccordionContent
+Accordion.Panel = AccordionPanel
Accordion.Title = AccordionTitle
export default Accordion
diff --git a/src/modules/Accordion/AccordionAccordion.d.ts b/src/modules/Accordion/AccordionAccordion.d.ts
index b7476c00bd..d51e05c3a3 100644
--- a/src/modules/Accordion/AccordionAccordion.d.ts
+++ b/src/modules/Accordion/AccordionAccordion.d.ts
@@ -1,7 +1,7 @@
import * as React from 'react'
import { SemanticShorthandCollection, SemanticShorthandItem } from '../../'
-import { AccordionContentProps } from './AccordionContent'
+import { AccordionPanelProps } from './AccordionPanel'
import { AccordionTitleProps } from './AccordionTitle'
export interface AccordionAccordionProps {
@@ -37,11 +37,6 @@ export interface AccordionAccordionProps {
panels?: SemanticShorthandCollection
}
-export interface AccordionPanelProps {
- content: SemanticShorthandItem
- title: SemanticShorthandItem
-}
-
declare const AccordionAccordion: React.ComponentClass
export default AccordionAccordion
diff --git a/src/modules/Accordion/AccordionAccordion.js b/src/modules/Accordion/AccordionAccordion.js
index 2304a8c90f..be3aec0326 100644
--- a/src/modules/Accordion/AccordionAccordion.js
+++ b/src/modules/Accordion/AccordionAccordion.js
@@ -5,13 +5,13 @@ import React from 'react'
import {
AutoControlledComponent as Component,
+ childrenUtils,
createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
} from '../../lib'
-import AccordionContent from './AccordionContent'
-import AccordionTitle from './AccordionTitle'
+import AccordionPanel from './AccordionPanel'
/**
* An Accordion can contain sub-accordions.
@@ -69,7 +69,7 @@ export default class AccordionAccordion extends Component {
static autoControlledProps = ['activeIndex']
getInitialAutoControlledState({ exclusive }) {
- return { activeIndex: exclusive ? -1 : [-1] }
+ return { activeIndex: exclusive ? -1 : [] }
}
computeNewIndex = (index) => {
@@ -77,21 +77,17 @@ export default class AccordionAccordion extends Component {
const { activeIndex } = this.state
if (exclusive) return index === activeIndex ? -1 : index
+
// check to see if index is in array, and remove it, if not then add it
return _.includes(activeIndex, index) ? _.without(activeIndex, index) : [...activeIndex, index]
}
- handleTitleOverrides = predefinedProps => ({
- onClick: (e, titleProps) => {
- const { index } = titleProps
- const activeIndex = this.computeNewIndex(index)
-
- this.trySetState({ activeIndex })
+ handleTitleClick = (e, titleProps) => {
+ const { index } = titleProps
- _.invoke(predefinedProps, 'onClick', e, titleProps)
- _.invoke(this.props, 'onTitleClick', e, titleProps)
- },
- })
+ this.trySetState({ activeIndex: this.computeNewIndex(index) })
+ _.invoke(this.props, 'onTitleClick', e, titleProps)
+ }
isIndexActive = (index) => {
const { exclusive } = this.props
@@ -100,28 +96,8 @@ export default class AccordionAccordion extends Component {
return exclusive ? activeIndex === index : _.includes(activeIndex, index)
}
- renderPanels = () => {
- const children = []
- const { panels } = this.props
-
- _.each(panels, (panel, index) => {
- const { content, title } = panel
- const active = this.isIndexActive(index)
-
- children.push(
- AccordionTitle.create(title, {
- defaultProps: { active, index },
- overrideProps: this.handleTitleOverrides,
- }),
- )
- children.push(AccordionContent.create(content, { defaultProps: { active } }))
- })
-
- return children
- }
-
render() {
- const { className, children } = this.props
+ const { className, children, panels } = this.props
const classes = cx('accordion', className)
const rest = getUnhandledProps(AccordionAccordion, this.props)
@@ -129,7 +105,17 @@ export default class AccordionAccordion extends Component {
return (
- {_.isNil(children) ? this.renderPanels() : children}
+ {childrenUtils.isNil(children)
+ ? _.map(panels, (panel, index) =>
+ AccordionPanel.create(panel, {
+ defaultProps: {
+ active: this.isIndexActive(index),
+ index,
+ onTitleClick: this.handleTitleClick,
+ },
+ }),
+ )
+ : children}
)
}
diff --git a/src/modules/Accordion/AccordionPanel.d.ts b/src/modules/Accordion/AccordionPanel.d.ts
new file mode 100644
index 0000000000..a158fdb942
--- /dev/null
+++ b/src/modules/Accordion/AccordionPanel.d.ts
@@ -0,0 +1,33 @@
+import * as React from 'react'
+
+import { SemanticShorthandItem } from '../../'
+import { AccordionContentProps } from './AccordionContent'
+import { AccordionTitleProps } from './AccordionTitle'
+
+export interface AccordionPanelProps {
+ [key: string]: any
+
+ /** Whether or not the title is in the open state. */
+ active?: boolean
+
+ /** A shorthand for Accordion.Content. */
+ content?: SemanticShorthandItem
+
+ /** A panel index. */
+ index?: number | string
+
+ /**
+ * Called when a panel title is clicked.
+ *
+ * @param {SyntheticEvent} event - React's original SyntheticEvent.
+ * @param {AccordionTitleProps} data - All item props.
+ */
+ onTitleClick?: (event: React.MouseEvent, data: AccordionTitleProps) => void
+
+ /** A shorthand for Accordion.Title. */
+ title?: SemanticShorthandItem
+}
+
+declare class AccordionPanel extends React.Component {}
+
+export default AccordionPanel
diff --git a/src/modules/Accordion/AccordionPanel.js b/src/modules/Accordion/AccordionPanel.js
new file mode 100644
index 0000000000..9c620bc4ef
--- /dev/null
+++ b/src/modules/Accordion/AccordionPanel.js
@@ -0,0 +1,61 @@
+import _ from 'lodash'
+import PropTypes from 'prop-types'
+import { Component } from 'react'
+
+import { createShorthandFactory, customPropTypes } from '../../lib'
+import AccordionTitle from './AccordionTitle'
+import AccordionContent from './AccordionContent'
+
+/**
+ * A panel sub-component for Accordion component.
+ */
+class AccordionPanel extends Component {
+ static propTypes = {
+ /** Whether or not the title is in the open state. */
+ active: PropTypes.bool,
+
+ /** A shorthand for Accordion.Content. */
+ content: customPropTypes.itemShorthand,
+
+ /** A panel index. */
+ index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+
+ /**
+ * Called when a panel title is clicked.
+ *
+ * @param {SyntheticEvent} event - React's original SyntheticEvent.
+ * @param {object} data - All item props.
+ */
+ onTitleClick: PropTypes.func,
+
+ /** A shorthand for Accordion.Title. */
+ title: customPropTypes.itemShorthand,
+ }
+
+ handleTitleOverrides = predefinedProps => ({
+ onClick: (e, titleProps) => {
+ _.invoke(predefinedProps, 'onClick', e, titleProps)
+ _.invoke(this.props, 'onTitleClick', e, titleProps)
+ },
+ })
+
+ render() {
+ const { active, content, index, title } = this.props
+
+ return [
+ AccordionTitle.create(title, {
+ autoGenerateKey: false,
+ defaultProps: { active, index, key: 'title' },
+ overrideProps: this.handleTitleOverrides,
+ }),
+ AccordionContent.create(content, {
+ autoGenerateKey: false,
+ defaultProps: { active, key: 'content' },
+ }),
+ ]
+ }
+}
+
+AccordionPanel.create = createShorthandFactory(AccordionPanel, null)
+
+export default AccordionPanel
diff --git a/src/modules/Modal/Modal.js b/src/modules/Modal/Modal.js
index e3ad504502..3d5cebacaa 100644
--- a/src/modules/Modal/Modal.js
+++ b/src/modules/Modal/Modal.js
@@ -7,6 +7,7 @@ import {
AutoControlledComponent as Component,
childrenUtils,
customPropTypes,
+ doesNodeContainClick,
getElementType,
getUnhandledProps,
isBrowser,
@@ -173,6 +174,16 @@ class Modal extends Component {
this.trySetState({ open: false })
}
+ handleDimmerClick = (e) => {
+ debug('handleDimmerClick()')
+ const { closeOnDimmerClick } = this.props
+
+ if (!closeOnDimmerClick || doesNodeContainClick(this.ref, e)) return
+
+ _.invoke(this.props, 'onClose', e, this.props)
+ this.trySetState({ open: false })
+ }
+
handleIconOverrides = predefinedProps => ({
onClick: (e) => {
_.invoke(predefinedProps, 'onClick', e)
@@ -205,15 +216,13 @@ class Modal extends Component {
handleRef = c => (this.ref = c)
- handlePortalRef = c => (this.portalRef = c)
-
- setRootNodeStyle = () => {
- debug('setRootNodeStyle()')
+ handleDimmerRef = c => (this.dimmerRef = c)
- if (!this.portalRef) return
+ setDimmerNodeStyle = () => {
+ debug('setDimmerNodeStyle()')
- if (this.portalRef) {
- this.portalRef.rootNode.style.setProperty('display', 'flex', 'important')
+ if (this.dimmerRef) {
+ this.dimmerRef.style.setProperty('display', 'flex', 'important')
}
}
@@ -256,7 +265,7 @@ class Modal extends Component {
this.animationRequestId = requestAnimationFrame(this.setPositionAndClassNames)
- this.setRootNodeStyle()
+ this.setDimmerNodeStyle()
}
renderContent = (rest) => {
@@ -318,14 +327,7 @@ class Modal extends Component {
render() {
const { open } = this.state
- const {
- centered,
- closeOnDimmerClick,
- closeOnDocumentClick,
- dimmer,
- eventPool,
- trigger,
- } = this.props
+ const { centered, closeOnDocumentClick, dimmer, eventPool, trigger } = this.props
const mountNode = this.getMountNode()
// Short circuit when server side rendering
@@ -369,10 +371,8 @@ class Modal extends Component {
return (
- {this.renderContent(rest)}
+
+ {this.renderContent(rest)}
+
)
}
diff --git a/src/modules/Popup/Popup.js b/src/modules/Popup/Popup.js
index 1e9eaf6782..0f4deb899a 100644
--- a/src/modules/Popup/Popup.js
+++ b/src/modules/Popup/Popup.js
@@ -18,7 +18,6 @@ import {
import Portal from '../../addons/Portal'
import PopupContent from './PopupContent'
import PopupHeader from './PopupHeader'
-import Ref from '../../addons/Ref'
const debug = makeDebugger('popup')
@@ -324,10 +323,9 @@ export default class Popup extends Component {
handleOpen = (e) => {
debug('handleOpen()')
- this.coords = this.getContext().getBoundingClientRect()
- const { onOpen } = this.props
- if (onOpen) onOpen(e, this.props)
+ this.coords = this.getContext().getBoundingClientRect()
+ _.invoke(this.props, 'onOpen', e, this.props)
}
handlePortalMount = (e) => {
@@ -420,18 +418,17 @@ export default class Popup extends Component {
debug('portal props:', mergedPortalProps)
return (
- [
-
- {popupJSX}
-
- ]
+
+ {popupJSX}
+
)
}
}
diff --git a/test/specs/addons/Portal/Portal-test.js b/test/specs/addons/Portal/Portal-test.js
index 0f495de125..9a17486ba5 100644
--- a/test/specs/addons/Portal/Portal-test.js
+++ b/test/specs/addons/Portal/Portal-test.js
@@ -1,28 +1,28 @@
import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
-import { unmountComponentAtNode } from 'react-dom'
import * as common from 'test/specs/commonTests'
import { domEvent, sandbox } from 'test/utils'
import Portal from 'src/addons/Portal/Portal'
+import PortalInner from 'src/addons/Portal/PortalInner'
-let attachTo
let wrapper
-const createHandlingComponent = eventName => class HandlingComponent extends Component {
- static propTypes = {
- handler: PropTypes.func,
- }
+const createHandlingComponent = eventName =>
+ class HandlingComponent extends Component {
+ static propTypes = {
+ handler: PropTypes.func,
+ }
- handleEvent = e => this.props.handler(e, this.props)
+ handleEvent = e => this.props.handler(e, this.props)
- render() {
- const buttonProps = { [eventName]: this.handleEvent }
+ render() {
+ const buttonProps = { [eventName]: this.handleEvent }
- return
+ return
+ }
}
-}
const wrapperMount = (node, opts) => {
wrapper = mount(node, opts)
@@ -30,123 +30,64 @@ const wrapperMount = (node, opts) => {
}
describe('Portal', () => {
- beforeEach(() => {
- document.body.innerHTML = ''
- attachTo = undefined
- wrapper = undefined
- })
-
afterEach(() => {
if (wrapper && wrapper.unmount) wrapper.unmount()
- if (attachTo) document.body.removeChild(attachTo)
})
+ common.hasSubcomponents(Portal, [PortalInner])
common.hasValidTypings(Portal)
it('propTypes.children should be required', () => {
Portal.propTypes.children.should.equal(PropTypes.node.isRequired)
})
- it('this.rootNode should be undefined if portal is not open', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- expect(instance.rootNode).to.equal(undefined)
- })
-
- it('attachRenderSubTreeSubscribers returns null if rootNode is lost', () => {
- wrapperMount()
- const instance = wrapper.instance()
- instance.rootNode = null
- expect(() => instance.attachRenderSubTreeSubscribers()).to.not.throw()
- expect(instance.attachRenderSubTreeSubscribers()).to.equal(null)
- })
-
- it('appends portal with children to the document.body', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- instance.rootNode.firstElementChild.tagName.should.equal('P')
- document.body.lastElementChild.should.equal(instance.rootNode)
- document.body.childElementCount.should.equal(1)
- })
-
it('does not call this.setState() if portal is unmounted', () => {
- const div = document.createElement('div')
- const props = { open: true }
- wrapperMount(, { attachTo: div })
-
- const spy = sandbox.spy(wrapper, 'setState')
- unmountComponentAtNode(div)
- spy.should.not.have.been.called()
+ wrapperMount(
+
+
+ ,
+ )
+
+ const setState = sandbox.spy(wrapper, 'setState')
+ wrapper.unmount()
+ setState.should.not.have.been.called()
})
describe('open', () => {
it('opens the portal when toggled from false to true', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- document.body.childElementCount.should.equal(0)
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
// Enzyme docs say it merges previous props but without children, react complains
wrapper.setProps({ open: true, children: })
- document.body.lastElementChild.should.equal(instance.rootNode)
- document.body.childElementCount.should.equal(1)
+ wrapper.should.have.descendants(PortalInner)
})
it('closes the portal when toggled from true to false ', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- document.body.lastElementChild.should.equal(instance.rootNode)
- document.body.childElementCount.should.equal(1)
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
wrapper.setProps({ open: false, children: })
- document.body.childElementCount.should.equal(0)
- })
- })
-
- describe('className', () => {
- it('is added to the portal\'s wrapping node', () => {
- wrapperMount()
-
- document.body.lastElementChild.className.should.equal('some-class')
- })
-
- it('updates the portal\'s wrapping node className when changed', () => {
- wrapperMount()
-
- wrapper.setProps({ className: 'some-other-class', children: })
- document.body.lastElementChild.className.should.equal('some-other-class')
- })
- })
-
- describe('prepend', () => {
- beforeEach(() => {
- document.body.innerHTML = ''
- })
-
- it('appends portal by default', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- document.body.childElementCount.should.equal(2)
- document.body.lastElementChild.should.equal(instance.rootNode)
- })
-
- it('prepends portal by when passed', () => {
- wrapperMount()
- const instance = wrapper.instance()
-
- document.body.childElementCount.should.equal(2)
- document.body.firstElementChild.should.equal(instance.rootNode)
+ wrapper.should.not.have.descendants(PortalInner)
})
})
describe('onMount', () => {
it('called when portal opens', () => {
const props = { open: false, onMount: sandbox.spy() }
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.setProps({ open: true, children: })
props.onMount.should.have.been.calledOnce()
@@ -154,7 +95,11 @@ describe('Portal', () => {
it('is not called when portal receives props', () => {
const props = { open: false, onMount: sandbox.spy() }
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.setProps({ open: true, children: , className: 'old' })
props.onMount.should.have.been.calledOnce()
@@ -167,7 +112,11 @@ describe('Portal', () => {
describe('onUnmount', () => {
it('is called when portal closes', () => {
const props = { open: true, onUnmount: sandbox.spy() }
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.setProps({ open: false, children: })
props.onUnmount.should.have.been.calledOnce()
@@ -175,7 +124,11 @@ describe('Portal', () => {
it('is not called when portal receives props', () => {
const props = { open: true, onUnmount: sandbox.spy() }
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.setProps({ open: false, children: , className: 'old' })
props.onUnmount.should.have.been.calledOnce()
@@ -185,42 +138,60 @@ describe('Portal', () => {
})
it('is called only once when portal closes and then is unmounted', () => {
- const div = document.createElement('div')
- const props = { open: true, onUnmount: sandbox.spy() }
- wrapperMount(, { attachTo: div })
+ const onUnmount = sandbox.spy()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.setProps({ open: false, children: })
- unmountComponentAtNode(div)
- props.onUnmount.should.have.been.calledOnce()
+ wrapper.unmount()
+ onUnmount.should.have.been.calledOnce()
})
it('is called only once when directly unmounting', () => {
- const div = document.createElement('div')
- const props = { open: true, onUnmount: sandbox.spy() }
+ const onUnmount = sandbox.spy()
+ wrapperMount(
+
+
+ ,
+ )
- wrapperMount(, { attachTo: div })
- unmountComponentAtNode(div)
- props.onUnmount.should.have.been.calledOnce()
+ wrapper.unmount()
+ onUnmount.should.have.been.calledOnce()
})
})
- describe('portal ref', () => {
+ describe('portalNode', () => {
it('maintains ref to DOM node with host element', () => {
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.instance().portalNode.tagName.should.equal('P')
})
it('maintains ref to DOM node with React component', () => {
const EmptyComponent = () =>
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.instance().portalNode.tagName.should.equal('P')
})
})
describe('trigger', () => {
it('renders null when not set', () => {
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
expect(wrapper.html()).to.equal(null)
})
@@ -228,7 +199,11 @@ describe('Portal', () => {
it('renders the trigger when set', () => {
const text = 'open by click on me'
const trigger =
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
wrapper.text().should.equal(text)
})
@@ -240,7 +215,11 @@ describe('Portal', () => {
const Trigger = createHandlingComponent(handlerName)
const trigger =
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
.find('button')
.simulate(_.toLower(handlerName.substring(2)), event)
@@ -254,15 +233,15 @@ describe('Portal', () => {
})
describe('mountNode', () => {
- it('render portal within mountNode', () => {
+ it('passed to PortalInner', () => {
const mountNode = document.createElement('div')
- document.body.appendChild(mountNode)
-
- wrapperMount()
- const instance = wrapper.instance()
+ wrapperMount(
+
+
+ ,
+ )
- mountNode.lastElementChild.should.equal(instance.rootNode)
- mountNode.childElementCount.should.equal(1)
+ wrapper.find(PortalInner).should.have.prop('mountNode', mountNode)
})
})
@@ -274,30 +253,47 @@ describe('Portal', () => {
it('does not open the portal on trigger click when false', () => {
const spy = sandbox.spy()
const trigger =
- wrapperMount()
+
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
wrapper.find('button').simulate('click')
- document.body.childElementCount.should.equal(0)
+ wrapper.should.not.have.descendants(PortalInner)
spy.should.have.been.calledOnce()
})
it('opens the portal on trigger click when true', () => {
const spy = sandbox.spy()
const trigger =
- wrapperMount()
+
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
wrapper.find('button').simulate('click')
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.should.have.descendants(PortalInner)
spy.should.have.been.calledOnce()
})
})
describe('closeOnTriggerClick', () => {
it('does not close the portal on click', () => {
- wrapperMount(} defaultOpen>)
+ wrapperMount(
+ } defaultOpen>
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
wrapper.find('button').simulate('click')
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.should.have.descendants(PortalInner)
})
it('closes the portal on click when set', () => {
@@ -306,29 +302,39 @@ describe('Portal', () => {
,
)
+ wrapper.should.have.descendants(PortalInner)
wrapper.find('button').simulate('click')
- document.body.childElementCount.should.equal(0)
+ wrapper.should.not.have.descendants(PortalInner)
})
})
describe('openOnTriggerMouseEnter', () => {
it('does not open the portal on mouseenter when not set', () => {
- wrapperMount(}>)
+ wrapperMount(
+ }>
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
wrapper.find('button').simulate('mouseenter')
- document.body.childElementCount.should.equal(0)
+ wrapper.should.not.have.descendants(PortalInner)
})
it('opens the portal on mouseenter when set', (done) => {
- wrapperMount(} openOnTriggerMouseEnter mouseEnterDelay={0}>)
+ wrapperMount(
+ } openOnTriggerMouseEnter mouseEnterDelay={0}>
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
- document.body.childElementCount.should.equal(0)
wrapper.find('button').simulate('mouseenter')
-
setTimeout(() => {
- document.body.childElementCount.should.equal(1)
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
+
done()
}, 1)
})
@@ -336,12 +342,18 @@ describe('Portal', () => {
describe('closeOnTriggerMouseLeave', () => {
it('does not close the portal on mouseleave when not set', (done) => {
- wrapperMount(} defaultOpen mouseLeaveDelay={0}>)
+ wrapperMount(
+ } defaultOpen mouseLeaveDelay={0}>
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
wrapper.find('button').simulate('mouseleave')
-
setTimeout(() => {
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
+
done()
}, 1)
})
@@ -352,12 +364,13 @@ describe('Portal', () => {
,
)
+ wrapper.should.have.descendants(PortalInner)
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
wrapper.find('button').simulate('mouseleave')
-
setTimeout(() => {
- document.body.childElementCount.should.equal(0)
+ wrapper.update()
+ wrapper.should.not.have.descendants(PortalInner)
+
done()
}, 1)
})
@@ -365,28 +378,35 @@ describe('Portal', () => {
describe('closeOnPortalMouseLeave', () => {
it('does not close the portal on mouseleave of portal when not set', (done) => {
- wrapperMount(} defaultOpen mouseLeaveDelay={0}>)
-
- domEvent.mouseLeave(wrapper.instance().rootNode.firstElementChild)
+ wrapperMount(
+ } defaultOpen mouseLeaveDelay={0}>
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
+ domEvent.mouseLeave('#inner')
setTimeout(() => {
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
+
done()
}, 1)
})
it('closes the portal on mouseleave of portal when set', (done) => {
wrapperMount(
- } defaultOpen closeOnPortalMouseLeave mouseLeaveDelay={0}>
-
+ }>
+
,
)
+ wrapper.should.have.descendants(PortalInner)
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
- domEvent.mouseLeave(wrapper.instance().rootNode.firstElementChild)
-
+ domEvent.mouseLeave('#inner')
setTimeout(() => {
- document.body.childElementCount.should.equal(0)
+ wrapper.update()
+ wrapper.should.not.have.descendants(PortalInner)
+
done()
}, 1)
})
@@ -396,19 +416,24 @@ describe('Portal', () => {
it('closes the portal on trigger mouseleave even when portal receives mouseenter within limit', (done) => {
const delay = 10
wrapperMount(
- } defaultOpen closeOnTriggerMouseLeave mouseLeaveDelay={delay}>,
+ } defaultOpen closeOnTriggerMouseLeave mouseLeaveDelay={delay}>
+
+ ,
)
+ wrapper.should.have.descendants(PortalInner)
wrapper.find('button').simulate('mouseleave')
// Fire a mouseEnter on the portal within the time limit
setTimeout(() => {
- domEvent.mouseEnter(wrapper.instance().rootNode.firstElementChild)
+ domEvent.mouseEnter('#inner')
}, delay - 1)
// The portal should close because closeOnPortalMouseLeave not set
setTimeout(() => {
- document.body.childElementCount.should.equal(0)
+ wrapper.update()
+ wrapper.should.not.have.descendants(PortalInner)
+
done()
}, delay + 1)
})
@@ -423,20 +448,23 @@ describe('Portal', () => {
closeOnPortalMouseLeave
mouseLeaveDelay={delay}
>
-
+
,
)
+ wrapper.should.have.descendants(PortalInner)
wrapper.find('button').simulate('mouseleave')
// Fire a mouseEnter on the portal within the time limit
setTimeout(() => {
- domEvent.mouseEnter(wrapper.instance().rootNode.firstElementChild)
+ domEvent.mouseEnter('#inner')
}, delay - 1)
// The portal should not have closed
setTimeout(() => {
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
+
done()
}, delay + 1)
})
@@ -444,68 +472,109 @@ describe('Portal', () => {
describe('openOnTriggerFocus', () => {
it('does not open the portal on focus when not set', () => {
- wrapperMount(}>)
- .find('button')
- .simulate('focus')
+ wrapperMount(
+ }>
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
- document.body.childElementCount.should.equal(0)
+ wrapper.find('button').simulate('focus')
+ wrapper.should.not.have.descendants(PortalInner)
})
+
it('opens the portal on focus when set', () => {
- wrapperMount(} openOnTriggerFocus>)
- .find('button')
- .simulate('focus')
+ wrapperMount(
+ } openOnTriggerFocus>
+
+ ,
+ )
+ wrapper.should.not.have.descendants(PortalInner)
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.find('button').simulate('focus')
+ wrapper.should.have.descendants(PortalInner)
})
})
describe('closeOnTriggerBlur', () => {
it('does not close the portal on blur when not set', () => {
- wrapperMount(} defaultOpen>)
- .find('button')
- .simulate('blur')
+ wrapperMount(
+ } defaultOpen>
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
- document.body.lastElementChild.should.equal(wrapper.instance().rootNode)
+ wrapper.find('button').simulate('blur')
+ wrapper.should.have.descendants(PortalInner)
})
it('closes the portal on blur when set', () => {
- wrapperMount(} defaultOpen closeOnTriggerBlur>)
- .find('button')
- .simulate('blur')
+ wrapperMount(
+ } defaultOpen closeOnTriggerBlur>
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
- document.body.childElementCount.should.equal(0)
+ wrapper.find('button').simulate('blur')
+ wrapper.should.not.have.descendants(PortalInner)
})
})
describe('closeOnEscape', () => {
it('closes the portal on escape', () => {
- wrapperMount()
- document.body.childElementCount.should.equal(1)
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
+
domEvent.keyDown(document, { key: 'Escape' })
- document.body.childElementCount.should.equal(0)
+ wrapper.update()
+ wrapper.should.not.have.descendants(PortalInner)
})
it('does not close the portal on escape when false', () => {
- wrapperMount()
- document.body.childElementCount.should.equal(1)
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
+
domEvent.keyDown(document, { key: 'Escape' })
- document.body.childElementCount.should.equal(1)
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
})
})
describe('closeOnDocumentClick', () => {
it('closes the portal on document click', () => {
- wrapperMount()
- document.body.childElementCount.should.equal(1)
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
domEvent.click(document)
- document.body.childElementCount.should.equal(0)
+ wrapper.update()
+ wrapper.should.not.have.descendants(PortalInner)
})
+
it('does not close on click inside', () => {
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
+ wrapper.should.have.descendants(PortalInner)
- domEvent.click(wrapper.instance().rootNode.firstElementChild)
- document.body.childElementCount.should.equal(1)
+ domEvent.click('#inner')
+ wrapper.update()
+ wrapper.should.have.descendants(PortalInner)
})
})
@@ -515,11 +584,14 @@ describe('Portal', () => {
// Leave these tests here to ensure we aren't ever stealing focus.
describe('focus', () => {
it('does not take focus onMount', (done) => {
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
setTimeout(() => {
- const { portalNode } = wrapper.instance()
- document.activeElement.should.not.equal(portalNode)
+ document.activeElement.should.not.equal(document.getElementById('inner'))
done()
}, 0)
})
@@ -531,7 +603,11 @@ describe('Portal', () => {
input.focus()
document.activeElement.should.equal(input)
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
document.activeElement.should.equal(input)
setTimeout(() => {
@@ -554,7 +630,11 @@ describe('Portal', () => {
input.focus()
document.activeElement.should.equal(input)
- wrapperMount()
+ wrapperMount(
+
+
+ ,
+ )
document.activeElement.should.equal(input)
setTimeout(() => {
@@ -568,4 +648,26 @@ describe('Portal', () => {
}, 0)
})
})
+
+ describe('triggerRef', () => {
+ it('maintains ref on the trigger', () => {
+ const triggerRef = sandbox.spy()
+ const mountNode = document.createElement('div')
+ document.body.appendChild(mountNode)
+
+ wrapperMount(
+ } triggerRef={triggerRef}>
+
+ ,
+ { attachTo: mountNode },
+ )
+ const trigger = document.querySelector('#trigger')
+
+ triggerRef.should.have.been.calledOnce()
+ triggerRef.should.have.been.calledWithMatch(trigger)
+
+ wrapper.detach()
+ document.body.removeChild(mountNode)
+ })
+ })
})
diff --git a/test/specs/addons/Portal/PortalInner-test.js b/test/specs/addons/Portal/PortalInner-test.js
new file mode 100644
index 0000000000..4fcf90134e
--- /dev/null
+++ b/test/specs/addons/Portal/PortalInner-test.js
@@ -0,0 +1,39 @@
+import React from 'react'
+
+import PortalInner from 'src/addons/Portal/PortalInner'
+import * as common from 'test/specs/commonTests'
+import { sandbox } from 'test/utils'
+
+describe('PortalInner', () => {
+ common.isConformant(PortalInner, {
+ rendersChildren: false,
+ requiredProps: { children: },
+ })
+
+ describe('onMount', () => {
+ it('called when mounting', () => {
+ const onMount = sandbox.spy()
+ mount(
+
+
+ ,
+ )
+
+ onMount.should.have.been.calledOnce()
+ })
+ })
+
+ describe('onUnmount', () => {
+ it('is called only once when unmounting', () => {
+ const onUnmount = sandbox.spy()
+ const wrapper = mount(
+
+
+ ,
+ )
+
+ wrapper.unmount()
+ onUnmount.should.have.been.calledOnce()
+ })
+ })
+})
diff --git a/test/specs/modules/Accordion/Accordion-test.js b/test/specs/modules/Accordion/Accordion-test.js
index 4013b2a6f0..19062fba5a 100644
--- a/test/specs/modules/Accordion/Accordion-test.js
+++ b/test/specs/modules/Accordion/Accordion-test.js
@@ -3,12 +3,18 @@ import React from 'react'
import Accordion from 'src/modules/Accordion/Accordion'
import AccordionAccordion from 'src/modules/Accordion/AccordionAccordion'
import AccordionContent from 'src/modules/Accordion/AccordionContent'
+import AccordionPanel from 'src/modules/Accordion/AccordionPanel'
import AccordionTitle from 'src/modules/Accordion/AccordionTitle'
import * as common from 'test/specs/commonTests'
describe('Accordion', () => {
common.isConformant(Accordion)
- common.hasSubcomponents(Accordion, [AccordionAccordion, AccordionContent, AccordionTitle])
+ common.hasSubcomponents(Accordion, [
+ AccordionAccordion,
+ AccordionContent,
+ AccordionPanel,
+ AccordionTitle,
+ ])
common.hasUIClassName(Accordion)
common.propKeyOnlyToClassName(Accordion, 'fluid')
diff --git a/test/specs/modules/Accordion/AccordionAccordion-test.js b/test/specs/modules/Accordion/AccordionAccordion-test.js
index b203bd69f3..df84b9544f 100644
--- a/test/specs/modules/Accordion/AccordionAccordion-test.js
+++ b/test/specs/modules/Accordion/AccordionAccordion-test.js
@@ -16,182 +16,138 @@ describe('AccordionAccordion', () => {
describe('activeIndex', () => {
const panels = [
- { title: 'A', content: 'Something A' },
- { title: 'B', content: 'Something B' },
- { title: 'C', content: 'Something C' },
+ { key: 'A', title: 'A', content: 'Something A' },
+ { key: 'B', title: 'B', content: 'Something B' },
+ { key: 'C', title: 'C', content: 'Something C' },
]
it('defaults to -1', () => {
- shallow()
- .should.have.state('activeIndex', -1)
+ shallow().should.have.state('activeIndex', -1)
+ })
+
+ it('defaults to -1 when "exclusive" is false', () => {
+ shallow()
+ .should.have.state('activeIndex')
+ .that.is.empty()
})
it('makes Accordion.Content at activeIndex - 0 "active"', () => {
- const contents = shallow()
- .find('AccordionContent')
+ const wrapper = shallow()
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', false)
+ wrapper.childAt(0).should.have.prop('active', true)
+ wrapper.childAt(1).should.have.prop('active', false)
+ wrapper.childAt(2).should.have.prop('active', false)
})
it('is toggled to -1 when clicking Title a second time', () => {
const wrapper = mount()
- // open panel (activeIndex 0)
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(0)
.simulate('click')
wrapper.should.have.state('activeIndex', 0)
- wrapper
- .find('AccordionTitle')
- .at(0)
- .should.have.prop('active', true)
- // close panel (activeIndex -1)
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(0)
.simulate('click')
wrapper.should.have.state('activeIndex', -1)
- wrapper
- .find('AccordionTitle')
- .at(0)
- .should.have.prop('active', false)
})
- it('sets the correct pair of title/content active', () => {
- const wrapper = shallow()
- wrapper.setProps({ activeIndex: 0 })
+ it('sets the correct panel active', () => {
+ const wrapper = shallow()
+
wrapper.childAt(0).should.have.prop('active', true)
- wrapper.childAt(1).should.have.prop('active', true)
+ wrapper.childAt(1).should.have.prop('active', false)
wrapper.childAt(2).should.have.prop('active', false)
- wrapper.childAt(3).should.have.prop('active', false)
wrapper.setProps({ activeIndex: 1 })
wrapper.childAt(0).should.have.prop('active', false)
- wrapper.childAt(1).should.have.prop('active', false)
- wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
+ wrapper.childAt(1).should.have.prop('active', true)
+ wrapper.childAt(2).should.have.prop('active', false)
})
it('can be an array', () => {
- const wrapper = shallow()
- wrapper.setProps({ activeIndex: [0, 1] })
+ const wrapper = shallow(
+ ,
+ )
wrapper.childAt(0).should.have.prop('active', true)
wrapper.childAt(1).should.have.prop('active', true)
- wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
- wrapper.childAt(4).should.have.prop('active', false)
- wrapper.childAt(5).should.have.prop('active', false)
+ wrapper.childAt(2).should.have.prop('active', false)
wrapper.setProps({ activeIndex: [1, 2] })
wrapper.childAt(0).should.have.prop('active', false)
- wrapper.childAt(1).should.have.prop('active', false)
+ wrapper.childAt(1).should.have.prop('active', true)
wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
- wrapper.childAt(4).should.have.prop('active', true)
- wrapper.childAt(5).should.have.prop('active', true)
})
it('can be inclusive and makes Accordion.Content at activeIndex - 1 "active"', () => {
- const contents = shallow()
- .find('AccordionTitle')
+ const wrapper = shallow(
+ ,
+ )
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', false)
+ wrapper.childAt(0).should.have.prop('active', true)
+ wrapper.childAt(1).should.have.prop('active', false)
+ wrapper.childAt(2).should.have.prop('active', false)
})
it('can be inclusive and allows multiple open', () => {
- const contents = shallow()
- .find('AccordionTitle')
+ const wrapper = shallow(
+ ,
+ )
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', true)
+ wrapper.childAt(0).should.have.prop('active', true)
+ wrapper.childAt(1).should.have.prop('active', true)
+ wrapper.childAt(2).should.have.prop('active', false)
})
it('can be inclusive and can open multiple panels by clicking', () => {
const wrapper = mount()
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(0)
.simulate('click')
- wrapper
- .find('AccordionTitle')
- .at(0)
- .should.have.prop('active', true)
- wrapper
- .find('AccordionContent')
- .at(0)
- .should.have.prop('active', true)
+ wrapper.should.have.state('activeIndex').that.includes(0)
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(1)
.simulate('click')
- wrapper
- .find('AccordionTitle')
- .at(1)
- .should.have.prop('active', true)
- wrapper
- .find('AccordionContent')
- .at(1)
- .should.have.prop('active', true)
+ wrapper.should.have.state('activeIndex').that.includes(0, 1)
})
it('can be inclusive and close multiple panels by clicking', () => {
- const wrapper = mount()
+ const wrapper = mount(
+ ,
+ )
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(0)
.simulate('click')
- wrapper
- .find('AccordionTitle')
- .at(0)
- .should.have.prop('active', false)
- wrapper
- .find('AccordionContent')
- .at(0)
- .should.have.prop('active', false)
+ wrapper.should.have.state('activeIndex').that.includes(1)
wrapper
- .find('AccordionTitle')
+ .find(AccordionTitle)
.at(1)
.simulate('click')
- wrapper
- .find('AccordionTitle')
- .at(1)
- .should.have.prop('active', false)
- wrapper
- .find('AccordionContent')
- .at(1)
- .should.have.prop('active', false)
+ wrapper.should.have.state('activeIndex').that.is.empty()
})
})
describe('defaultActiveIndex', () => {
it('sets the initial activeIndex state', () => {
- shallow()
- .should.have.state('activeIndex', 123)
+ shallow().should.have.state('activeIndex', 123)
})
})
describe('onTitleClick', () => {
const event = { target: null }
-
const onClick = sandbox.spy()
const onTitleClick = sandbox.spy()
- const panels = [
- { title: { content: 'A', onClick } },
- { title: 'B' },
- ]
-
- it('can be omitted', () => {
- const click = () => mount().find(AccordionTitle).at(1).simulate('click')
- expect(click).to.not.throw()
- })
+ const panels = [{ key: 'A', title: { content: 'A', onClick } }, { key: 'B', title: 'B' }]
it('is called with (e, titleProps) when clicked', () => {
mount()
@@ -209,11 +165,15 @@ describe('AccordionAccordion', () => {
describe('panels', () => {
const event = { target: null }
- const spy = sandbox.spy()
+ const onClick = sandbox.spy()
const panels = [
- { title: { content: 'A', onClick: spy }, content: { content: 'Content A', 'data-foo': 'something' } },
- { title: 'B', content: { content: 'Content B', 'data-foo': 'something' } },
+ {
+ key: 'A',
+ title: { content: 'A', onClick },
+ content: { content: 'Content A', 'data-foo': 'something' },
+ },
+ { key: 'B', title: 'B', content: { content: 'Content B', 'data-foo': 'something' } },
]
const children = mount()
@@ -228,20 +188,20 @@ describe('AccordionAccordion', () => {
contents.at(1).should.have.prop('content', 'Content B')
})
- it('onClick can omitted', () => {
- const click = () => children.find(AccordionTitle).at(1).simulate('click')
- expect(click).to.not.throw()
- })
-
it('passes onClick handler', () => {
- children.find(AccordionTitle).at(0).simulate('click', event)
+ children
+ .find(AccordionTitle)
+ .at(0)
+ .simulate('click', event)
- spy.should.have.been.calledOnce()
- spy.should.have.been.calledWithMatch(event, { content: 'A', index: 0 })
+ onClick.should.have.been.calledOnce()
+ onClick.should.have.been.calledWithMatch(event, { content: 'A', index: 0 })
})
it('passes arbitrary props', () => {
- children.find(AccordionContent).everyWhere(item => item.should.have.prop('data-foo', 'something'))
+ children
+ .find(AccordionContent)
+ .everyWhere(item => item.should.have.prop('data-foo', 'something'))
})
})
})
diff --git a/test/specs/modules/Accordion/AccordionPanel-test.js b/test/specs/modules/Accordion/AccordionPanel-test.js
new file mode 100644
index 0000000000..bf761612b5
--- /dev/null
+++ b/test/specs/modules/Accordion/AccordionPanel-test.js
@@ -0,0 +1,68 @@
+import React from 'react'
+
+import AccordionPanel from 'src/modules/Accordion/AccordionPanel'
+import AccordionTitle from 'src/modules/Accordion/AccordionTitle'
+import * as common from 'test/specs/commonTests'
+import { sandbox } from 'test/utils'
+
+describe('AccordionPanel', () => {
+ common.isConformant(AccordionPanel, { rendersChildren: false })
+
+ // TODO: Reenable tests in future
+ // https://github.com/airbnb/enzyme/issues/1553
+ //
+ // common.implementsShorthandProp(AccordionPanel, {
+ // assertExactMatch: false,
+ // propKey: 'content',
+ // ShorthandComponent: AccordionContent,
+ // mapValueToProps: content => ({ content }),
+ // })
+ // common.implementsShorthandProp(AccordionPanel, {
+ // propKey: 'title',
+ // ShorthandComponent: AccordionTitle,
+ // mapValueToProps: content => ({ content }),
+ // })
+
+ describe('active', () => {
+ it('should passed to children', () => {
+ const wrapper = shallow().at(0)
+
+ wrapper.at(0).should.have.prop('active', true)
+ wrapper.at(1).should.have.prop('active', true)
+ })
+ })
+
+ describe('index', () => {
+ it('should passed to title', () => {
+ const wrapper = shallow().at(0)
+
+ wrapper.at(0).should.have.prop('index', 5)
+ wrapper.at(1).should.have.not.prop('index')
+ })
+ })
+
+ describe('onTitleClick', () => {
+ it('is called with (e, titleProps) when clicked', () => {
+ const event = { target: null }
+ const onClick = sandbox.spy()
+ const onTitleClick = sandbox.spy()
+
+ mount(
+ ,
+ )
+ .find(AccordionTitle)
+ .at(0)
+ .simulate('click', event)
+
+ onClick.should.have.been.calledOnce()
+ onClick.should.have.been.calledWithMatch(event, { content: 'Title' })
+
+ onTitleClick.should.have.been.calledOnce()
+ onTitleClick.should.have.been.calledWithMatch(event, { content: 'Title' })
+ })
+ })
+})
diff --git a/test/specs/modules/Popup/Popup-test.js b/test/specs/modules/Popup/Popup-test.js
index 6662eec840..63a5d8fc09 100644
--- a/test/specs/modules/Popup/Popup-test.js
+++ b/test/specs/modules/Popup/Popup-test.js
@@ -1,7 +1,7 @@
import _ from 'lodash'
import React from 'react'
-import Ref from 'src/addons/Ref/Ref'
+import Portal from 'src/addons/Portal/Portal'
import { SUI } from 'src/lib'
import Popup, { POSITIONS } from 'src/modules/Popup/Popup'
import PopupHeader from 'src/modules/Popup/PopupHeader'
@@ -50,7 +50,7 @@ describe('Popup', () => {
it('renders a Portal', () => {
wrapperShallow()
.type()
- .should.equal(Ref)
+ .should.equal(Portal)
})
it('renders to the document body', () => {
@@ -156,10 +156,10 @@ describe('Popup', () => {
it('is positioned properly when open property is set', () => {
wrapperMount(foo} />)
const element = document.querySelector('.popup.ui')
- element.style.should.not.have.property('top', '')
- element.style.should.not.have.property('left', '')
- element.style.should.not.have.property('bottom', '')
- element.style.should.not.have.property('right', '')
+ element.style.should.have.property('top', '')
+ element.style.should.have.property('left', '')
+ element.style.should.have.property('bottom', '')
+ element.style.should.have.property('right', '')
})
it('is the original if no horizontal position fits within the viewport', () => {
wrapperMount(