diff --git a/docs/src/examples/addons/MountNode/index.js b/docs/src/examples/addons/MountNode/index.js
index 4b40401268..009db17183 100644
--- a/docs/src/examples/addons/MountNode/index.js
+++ b/docs/src/examples/addons/MountNode/index.js
@@ -1,8 +1,26 @@
import React from 'react'
+import { Icon, Message } from 'semantic-ui-react'
+
import Types from './Types'
const MountNodeExamples = () => (
+
+
+
+
+ Deprecation notice
+
+ MountNode
component is deprecated and will be removed in
+ the next major release. Please follow our{' '}
+
+ upgrade guide
+
+ .
+
+
+
+
)
diff --git a/src/addons/MountNode/MountNode.js b/src/addons/MountNode/MountNode.js
index 3ee0c9ba9e..def438faea 100644
--- a/src/addons/MountNode/MountNode.js
+++ b/src/addons/MountNode/MountNode.js
@@ -1,44 +1,23 @@
import PropTypes from 'prop-types'
-import { Component } from 'react'
+import React from 'react'
-import { customPropTypes } from '../../lib'
-import getNodeRefFromProps from './lib/getNodeRefFromProps'
-import handleClassNamesChange from './lib/handleClassNamesChange'
-import NodeRegistry from './lib/NodeRegistry'
-
-const nodeRegistry = new NodeRegistry()
+import { customPropTypes, useClassNamesOnNode } from '../../lib'
/**
* A component that allows to manage classNames on a DOM node in declarative manner.
+ *
+ * @deprecated This component is deprecated and will be removed in next major release.
*/
-export default class MountNode extends Component {
- shouldComponentUpdate({ className: nextClassName }) {
- const { className: currentClassName } = this.props
-
- return nextClassName !== currentClassName
- }
-
- componentDidMount() {
- const nodeRef = getNodeRefFromProps(this.props)
-
- nodeRegistry.add(nodeRef, this)
- nodeRegistry.emit(nodeRef, handleClassNamesChange)
- }
+function MountNode(props) {
+ useClassNamesOnNode(props.node, props.className)
- componentDidUpdate() {
- nodeRegistry.emit(getNodeRefFromProps(this.props), handleClassNamesChange)
+ // A workaround for `react-docgen`: https://github.com/reactjs/react-docgen/issues/336
+ if (process.env.NODE_ENV === 'test') {
+ return
}
- componentWillUnmount() {
- const nodeRef = getNodeRefFromProps(this.props)
-
- nodeRegistry.del(nodeRef, this)
- nodeRegistry.emit(nodeRef, handleClassNamesChange)
- }
-
- render() {
- return null
- }
+ /* istanbul ignore next */
+ return null
}
MountNode.propTypes = {
@@ -48,3 +27,5 @@ MountNode.propTypes = {
/** The DOM node where we will apply class names. Defaults to document.body. */
node: PropTypes.oneOfType([customPropTypes.domNode, customPropTypes.refObject]),
}
+
+export default MountNode
diff --git a/src/addons/MountNode/lib/NodeRegistry.js b/src/addons/MountNode/lib/NodeRegistry.js
deleted file mode 100644
index 90c5e42d07..0000000000
--- a/src/addons/MountNode/lib/NodeRegistry.js
+++ /dev/null
@@ -1,33 +0,0 @@
-export default class NodeRegistry {
- constructor() {
- this.nodes = new Map()
- }
-
- add = (nodeRef, component) => {
- if (this.nodes.has(nodeRef)) {
- const set = this.nodes.get(nodeRef)
-
- set.add(component)
- return
- }
-
- this.nodes.set(nodeRef, new Set([component]))
- }
-
- del = (nodeRef, component) => {
- if (!this.nodes.has(nodeRef)) return
-
- const set = this.nodes.get(nodeRef)
-
- if (set.size === 1) {
- this.nodes.delete(nodeRef)
- return
- }
-
- set.delete(component)
- }
-
- emit = (nodeRef, callback) => {
- callback(nodeRef, this.nodes.get(nodeRef))
- }
-}
diff --git a/src/addons/MountNode/lib/computeClassNames.js b/src/addons/MountNode/lib/computeClassNames.js
deleted file mode 100644
index 080a1ee566..0000000000
--- a/src/addons/MountNode/lib/computeClassNames.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import _ from 'lodash/fp'
-
-const computeClassNames = _.flow(
- _.toArray,
- _.map('props.className'),
- _.flatMap(_.split(/\s+/)),
- _.filter(_.identity),
- _.uniq,
-)
-
-export default computeClassNames
diff --git a/src/addons/MountNode/lib/computeClassNamesDifference.js b/src/addons/MountNode/lib/computeClassNamesDifference.js
deleted file mode 100644
index b2f2905857..0000000000
--- a/src/addons/MountNode/lib/computeClassNamesDifference.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import _ from 'lodash'
-
-const computeClassNamesDifference = (prevClassNames, currentClassNames) => [
- _.difference(currentClassNames, prevClassNames),
- _.difference(prevClassNames, currentClassNames),
-]
-
-export default computeClassNamesDifference
diff --git a/src/addons/MountNode/lib/getNodeRefFromProps.js b/src/addons/MountNode/lib/getNodeRefFromProps.js
deleted file mode 100644
index 4c7644bece..0000000000
--- a/src/addons/MountNode/lib/getNodeRefFromProps.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { isRefObject, toRefObject } from '@stardust-ui/react-component-ref'
-import _ from 'lodash'
-
-import { isBrowser } from '../../../lib'
-
-/**
- * Given `this.props`, return a `node` value or undefined.
- *
- * @param {object|React.RefObject} props Component's props
- * @return {React.RefObject|undefined}
- */
-const getNodeRefFromProps = (props) => {
- const { node } = props
-
- if (isBrowser()) {
- if (isRefObject(node)) return node
- return _.isNil(node) ? toRefObject(document.body) : toRefObject(node)
- }
-}
-
-export default getNodeRefFromProps
diff --git a/src/addons/MountNode/lib/handleClassNamesChange.js b/src/addons/MountNode/lib/handleClassNamesChange.js
deleted file mode 100644
index 7a8d811271..0000000000
--- a/src/addons/MountNode/lib/handleClassNamesChange.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import _ from 'lodash'
-
-import computeClassNames from './computeClassNames'
-import computeClassNamesDifference from './computeClassNamesDifference'
-
-const prevClassNames = new Map()
-
-/**
- * @param {React.RefObject} nodeRef
- * @param {Object[]} components
- */
-const handleClassNamesChange = (nodeRef, components) => {
- const currentClassNames = computeClassNames(components)
- const [forAdd, forRemoval] = computeClassNamesDifference(
- prevClassNames.get(nodeRef),
- currentClassNames,
- )
-
- if (nodeRef.current) {
- _.forEach(forAdd, (className) => nodeRef.current.classList.add(className))
- _.forEach(forRemoval, (className) => nodeRef.current.classList.remove(className))
- }
-
- prevClassNames.set(nodeRef, currentClassNames)
-}
-
-export default handleClassNamesChange
diff --git a/src/lib/hooks/useClassNamesOnNode.js b/src/lib/hooks/useClassNamesOnNode.js
new file mode 100644
index 0000000000..6de281e099
--- /dev/null
+++ b/src/lib/hooks/useClassNamesOnNode.js
@@ -0,0 +1,145 @@
+import React from 'react'
+import { isRefObject } from '@stardust-ui/react-component-ref'
+
+import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'
+
+const CLASS_NAME_DELITIMITER = /\s+/
+
+/**
+ * Accepts a set of ref objects that contain classnames as a string and returns an array of unique
+ * classNames.
+ *
+ * @param {Set|undefined} classNameRefs
+ * @returns String[]
+ */
+export function computeClassNames(classNameRefs) {
+ const classNames = []
+
+ if (classNameRefs) {
+ classNameRefs.forEach((classNameRef) => {
+ if (typeof classNameRef.current === 'string') {
+ const classNamesForRef = classNameRef.current.split(CLASS_NAME_DELITIMITER)
+
+ classNamesForRef.forEach((className) => {
+ classNames.push(className)
+ })
+ }
+ })
+
+ return classNames.filter(
+ (className, i, array) => className.length > 0 && array.indexOf(className) === i,
+ )
+ }
+
+ return []
+}
+
+/**
+ * Computes classnames that should be removed and added to a node based on input differences.
+ *
+ * @param {String[]} prevClassNames
+ * @param {String[]} currentClassNames
+ */
+export function computeClassNamesDifference(prevClassNames, currentClassNames) {
+ return [
+ currentClassNames.filter((className) => prevClassNames.indexOf(className) === -1),
+ prevClassNames.filter((className) => currentClassNames.indexOf(className) === -1),
+ ]
+}
+
+const prevClassNames = new Map()
+
+/**
+ * @param {HTMLElement} node
+ * @param {Set|undefined} classNameRefs
+ */
+export const handleClassNamesChange = (node, classNameRefs) => {
+ const currentClassNames = computeClassNames(classNameRefs)
+ const [forAdd, forRemoval] = computeClassNamesDifference(
+ prevClassNames.get(node) || [],
+ currentClassNames,
+ )
+
+ if (node) {
+ forAdd.forEach((className) => node.classList.add(className))
+ forRemoval.forEach((className) => node.classList.remove(className))
+ }
+
+ prevClassNames.set(node, currentClassNames)
+}
+
+export class NodeRegistry {
+ constructor() {
+ this.nodes = new Map()
+ }
+
+ add = (node, classNameRef) => {
+ if (this.nodes.has(node)) {
+ const set = this.nodes.get(node)
+
+ set.add(classNameRef)
+ return
+ }
+
+ // IE11 does not support constructor params
+ const set = new Set()
+ set.add(classNameRef)
+
+ this.nodes.set(node, set)
+ }
+
+ del = (node, classNameRef) => {
+ if (!this.nodes.has(node)) {
+ return
+ }
+
+ const set = this.nodes.get(node)
+
+ if (set.size === 1) {
+ this.nodes.delete(node)
+ return
+ }
+
+ set.delete(classNameRef)
+ }
+
+ emit = (node, callback) => {
+ callback(node, this.nodes.get(node))
+ }
+}
+
+const nodeRegistry = new NodeRegistry()
+
+/**
+ * A React hooks that allows to manage classNames on a DOM node in declarative manner. Accepts
+ * a HTML element or React ref objects with it.
+ *
+ * @param {HTMLElement|React.RefObject} node
+ * @param {String} className
+ */
+export default function useClassNamesOnNode(node, className) {
+ const classNameRef = React.useRef()
+ const isMounted = React.useRef(false)
+
+ useIsomorphicLayoutEffect(() => {
+ classNameRef.current = className
+
+ if (isMounted.current) {
+ const element = isRefObject(node) ? node.current : node
+ nodeRegistry.emit(element, handleClassNamesChange)
+ }
+
+ isMounted.current = true
+ }, [className])
+ useIsomorphicLayoutEffect(() => {
+ const element = isRefObject(node) ? node.current : node
+
+ nodeRegistry.add(element, classNameRef)
+ nodeRegistry.emit(element, handleClassNamesChange)
+
+ return () => {
+ nodeRegistry.del(element, classNameRef)
+ nodeRegistry.emit(element, handleClassNamesChange)
+ }
+ }, [node])
+}
diff --git a/src/lib/hooks/useIsomorphicLayoutEffect.js b/src/lib/hooks/useIsomorphicLayoutEffect.js
new file mode 100644
index 0000000000..4b80dac212
--- /dev/null
+++ b/src/lib/hooks/useIsomorphicLayoutEffect.js
@@ -0,0 +1,9 @@
+import React from 'react'
+import isBrowser from '../isBrowser'
+
+// useLayoutEffect() produces a warning with SSR rendering
+// https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a
+const useIsomorphicLayoutEffect =
+ isBrowser() && process.env.NODE_ENV !== 'test' ? React.useLayoutEffect : React.useEffect
+
+export default useIsomorphicLayoutEffect
diff --git a/src/lib/index.js b/src/lib/index.js
index 4c0e8bb44e..808896b7e9 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -41,3 +41,9 @@ export objectDiff from './objectDiff'
// Heads up! We import/export for this module to safely remove it with "babel-plugin-filter-imports"
export { makeDebugger }
+
+//
+// Hooks
+//
+
+export useClassNamesOnNode from './hooks/useClassNamesOnNode'
diff --git a/src/modules/Modal/ModalDimmer.js b/src/modules/Modal/ModalDimmer.js
index 884bc83115..07da858247 100644
--- a/src/modules/Modal/ModalDimmer.js
+++ b/src/modules/Modal/ModalDimmer.js
@@ -3,13 +3,13 @@ import cx from 'clsx'
import PropTypes from 'prop-types'
import React from 'react'
-import MountNode from '../../addons/MountNode'
import {
childrenUtils,
createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
+ useClassNamesOnNode,
useKeyOnly,
} from '../../lib'
@@ -36,6 +36,7 @@ function ModalDimmer(props) {
const rest = getUnhandledProps(ModalDimmer, props)
const ElementType = getElementType(ModalDimmer, props)
+ useClassNamesOnNode(mountNode, bodyClasses)
React.useEffect(() => {
if (ref.current && ref.current.style) {
ref.current.style.setProperty('display', 'flex', 'important')
@@ -46,8 +47,6 @@ function ModalDimmer(props) {
[
{childrenUtils.isNil(children) ? content : children}
-
-
]
)
diff --git a/test/specs/addons/MountNode/MountNode-test.js b/test/specs/addons/MountNode/MountNode-test.js
index 833a862dca..41636ee6d1 100644
--- a/test/specs/addons/MountNode/MountNode-test.js
+++ b/test/specs/addons/MountNode/MountNode-test.js
@@ -35,20 +35,4 @@ describe('MountNode', () => {
node.classList.contains('foo').should.be.equal(false)
})
})
-
- describe('shouldComponentUpdate', () => {
- it('will not rerender when nextClassName is same', () => {
- const wrapper = shallow()
- const shouldUpdate = wrapper.instance().shouldComponentUpdate({ className: 'foo' })
-
- shouldUpdate.should.be.equal(false)
- })
-
- it('will rerender when nextClassName is another', () => {
- const wrapper = shallow()
- const shouldUpdate = wrapper.instance().shouldComponentUpdate({ className: 'bar' })
-
- shouldUpdate.should.be.equal(true)
- })
- })
})
diff --git a/test/specs/addons/MountNode/lib/computeClassNameDifference-test.js b/test/specs/addons/MountNode/lib/computeClassNameDifference-test.js
deleted file mode 100644
index 31ba002aba..0000000000
--- a/test/specs/addons/MountNode/lib/computeClassNameDifference-test.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import _ from 'lodash'
-import computeClassNamesDifference from 'src/addons/MountNode/lib/computeClassNamesDifference'
-
-const fixtures = [
- {
- prevClasses: [],
- currentClasses: [],
- forAdd: [],
- forRemoval: [],
- },
- {
- prevClasses: ['foo', 'bar'],
- currentClasses: ['bar', 'baz'],
- forAdd: ['baz'],
- forRemoval: ['foo'],
- },
-]
-
-describe('computeClassNamesDifference', () => {
- it('computes className difference', () => {
- _.forEach(fixtures, (fixture) => {
- const { prevClasses, currentClasses, forAdd, forRemoval } = fixture
-
- computeClassNamesDifference(prevClasses, currentClasses).should.have.deep.members([
- forAdd,
- forRemoval,
- ])
- })
- })
-})
diff --git a/test/specs/addons/MountNode/lib/getNodeRefFromProps-test.js b/test/specs/addons/MountNode/lib/getNodeRefFromProps-test.js
deleted file mode 100644
index 873aceaa4a..0000000000
--- a/test/specs/addons/MountNode/lib/getNodeRefFromProps-test.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import getNodeRefFromProps from 'src/addons/MountNode/lib/getNodeRefFromProps'
-import isBrowser from 'src/lib/isBrowser'
-
-describe('getNodeRefFromProps', () => {
- describe('browser', () => {
- it('returns a ref to node when it defined', () => {
- const node = document.createElement('div')
- const nodeRef = getNodeRefFromProps({ node })
-
- nodeRef.should.have.property('current', node)
- })
-
- it('returns node when it defined as React.Ref object', () => {
- const inputRef = { current: document.createElement('div') }
- const outputRef = getNodeRefFromProps({ node: inputRef })
-
- outputRef.should.equal(inputRef)
- })
-
- it('returns document.body by default', () => {
- getNodeRefFromProps({}).should.have.property('current', document.body)
- })
- })
-
- describe('browser', () => {
- before(() => {
- isBrowser.override = false
- })
-
- after(() => {
- isBrowser.override = null
- })
-
- it('always returns null', () => {
- expect(getNodeRefFromProps({ node: 'foo' })).to.be.a('undefined')
- expect(getNodeRefFromProps({})).to.be.a('undefined')
- })
- })
-})
diff --git a/test/specs/addons/MountNode/lib/NodeRegistry-test.js b/test/specs/lib/hooks/NodeRegistry-test.js
similarity index 97%
rename from test/specs/addons/MountNode/lib/NodeRegistry-test.js
rename to test/specs/lib/hooks/NodeRegistry-test.js
index 466b1bcdf5..877e0efdf8 100644
--- a/test/specs/addons/MountNode/lib/NodeRegistry-test.js
+++ b/test/specs/lib/hooks/NodeRegistry-test.js
@@ -1,4 +1,4 @@
-import NodeRegistry from 'src/addons/MountNode/lib/NodeRegistry'
+import { NodeRegistry } from 'src/lib/hooks/useClassNamesOnNode'
import { sandbox } from 'test/utils'
describe('NodeRegistry', () => {
diff --git a/test/specs/lib/hooks/computeClassNameDifference-test.js b/test/specs/lib/hooks/computeClassNameDifference-test.js
new file mode 100644
index 0000000000..f8d82a0c4f
--- /dev/null
+++ b/test/specs/lib/hooks/computeClassNameDifference-test.js
@@ -0,0 +1,33 @@
+import { computeClassNamesDifference } from 'src/lib/hooks/useClassNamesOnNode'
+
+const fixtures = [
+ {
+ prevClasses: [],
+ currentClasses: [],
+ forAdd: [],
+ forRemoval: [],
+ },
+ {
+ prevClasses: ['foo', 'bar'],
+ currentClasses: ['bar', 'baz'],
+ forAdd: ['baz'],
+ forRemoval: ['foo'],
+ },
+ {
+ prevClasses: ['foo', 'bar'],
+ currentClasses: ['foo', 'bar'],
+ forAdd: [],
+ forRemoval: [],
+ },
+]
+
+describe('computeClassNamesDifference', () => {
+ it('computes className difference', () => {
+ fixtures.forEach((fixture) => {
+ computeClassNamesDifference(
+ fixture.prevClasses,
+ fixture.currentClasses,
+ ).should.have.deep.members([fixture.forAdd, fixture.forRemoval])
+ })
+ })
+})
diff --git a/test/specs/addons/MountNode/lib/computeClassNames-test.js b/test/specs/lib/hooks/computeClassNames-test.js
similarity index 50%
rename from test/specs/addons/MountNode/lib/computeClassNames-test.js
rename to test/specs/lib/hooks/computeClassNames-test.js
index f422ecfda1..a427548c8f 100644
--- a/test/specs/addons/MountNode/lib/computeClassNames-test.js
+++ b/test/specs/lib/hooks/computeClassNames-test.js
@@ -1,4 +1,4 @@
-import computeClassNames from 'src/addons/MountNode/lib/computeClassNames'
+import { computeClassNames } from 'src/lib/hooks/useClassNamesOnNode'
describe('computeClassNames', () => {
it('accepts Set as value', () => {
@@ -9,41 +9,38 @@ describe('computeClassNames', () => {
})
it('combines classNames', () => {
- const map = new Set([{ props: { className: 'foo' } }, { props: { className: 'bar' } }])
+ const map = new Set([{ current: 'foo' }, { current: 'bar' }])
computeClassNames(map).should.have.members(['foo', 'bar'])
})
it('combines only unique classNames', () => {
- const map = new Set([
- { props: { className: 'foo' } },
- { props: { className: 'bar' } },
- { props: { className: 'foo bar baz' } },
- ])
+ const map = new Set([{ current: 'foo' }, { current: 'bar' }, { current: 'foo bar baz' }])
computeClassNames(map).should.have.members(['foo', 'bar', 'baz'])
})
it('omits false, undefined and null classNames', () => {
const map = new Set([
- { props: { className: 'foo' } },
- { props: {} },
- { props: { className: false } },
- { props: { className: null } },
- { props: { className: undefined } },
- { props: { className: '0' } },
- { props: { className: 'false' } },
+ { current: 'foo' },
+ {},
+ { current: false },
+ { current: null },
+ { current: undefined },
+ { current: '0' },
+ { current: 'false' },
])
computeClassNames(map).should.have.members(['foo', '0', 'false'])
})
it('trims classNames', () => {
- const map = new Set([
- { props: { className: ' foo bar ' } },
- { props: { className: ' baz qux' } },
- ])
+ const map = new Set([{ current: ' foo bar ' }, { current: ' baz qux' }])
computeClassNames(map).should.have.members(['foo', 'bar', 'baz', 'qux'])
})
+
+ it('skips "undefined" as input', () => {
+ computeClassNames([]).should.have.length(0)
+ })
})
diff --git a/test/specs/addons/MountNode/lib/handleClassNamesChange-test.js b/test/specs/lib/hooks/handleClassNamesChange-test.js
similarity index 54%
rename from test/specs/addons/MountNode/lib/handleClassNamesChange-test.js
rename to test/specs/lib/hooks/handleClassNamesChange-test.js
index 0e112493ce..9d6c551f1d 100644
--- a/test/specs/addons/MountNode/lib/handleClassNamesChange-test.js
+++ b/test/specs/lib/hooks/handleClassNamesChange-test.js
@@ -1,23 +1,21 @@
-import handleClassNamesChange from 'src/addons/MountNode/lib/handleClassNamesChange'
+import { handleClassNamesChange } from 'src/lib/hooks/useClassNamesOnNode'
import { sandbox } from 'test/utils'
-const FooComponent = { props: { className: 'foo' } }
-const BarComponent = { props: { className: 'bar' } }
+const fooRef = { current: 'foo' }
+const barRef = { current: 'bar' }
const nodes = new Set()
-const createNodeRefMock = (add, remove) => {
- const nodeRef = {
- current: {
- classList: { add, remove },
- },
+const createNodeMock = (add, remove) => {
+ const node = {
+ classList: { add, remove },
reset: () => {
add.resetHistory()
remove.resetHistory()
},
}
- nodes.add(nodeRef)
+ nodes.add(node)
- return nodeRef
+ return node
}
describe('handleClassNamesChange', () => {
@@ -28,10 +26,11 @@ describe('handleClassNamesChange', () => {
it('adds new classes to node', () => {
const add = sandbox.spy()
const remove = sandbox.spy()
- const components = new Set([FooComponent, BarComponent])
- const nodeRef = createNodeRefMock(add, remove)
- handleClassNamesChange(nodeRef, components)
+ const refs = new Set([fooRef, barRef])
+ const node = createNodeMock(add, remove)
+
+ handleClassNamesChange(node, refs)
add.should.have.been.calledTwice()
add.should.have.been.calledWith('foo')
add.should.have.been.calledWith('bar')
@@ -41,18 +40,20 @@ describe('handleClassNamesChange', () => {
it('removes nonexistent classes', () => {
const add = sandbox.spy()
const remove = sandbox.spy()
- const components = new Set([FooComponent, BarComponent])
- const nodeRef = createNodeRefMock(add, remove)
- handleClassNamesChange(nodeRef, components)
+ const refs = new Set([fooRef, barRef])
+ const node = createNodeMock(add, remove)
+
+ handleClassNamesChange(node, refs)
add.should.have.been.calledTwice()
add.should.have.been.calledWith('foo')
add.should.have.been.calledWith('bar')
remove.should.have.not.been.called()
- nodeRef.reset()
- components.delete(BarComponent)
- handleClassNamesChange(nodeRef, components)
+ node.reset()
+ refs.delete(barRef)
+
+ handleClassNamesChange(node, refs)
add.should.have.not.been.called()
remove.should.have.been.calledOnce()
remove.should.have.been.calledWith('bar')
@@ -61,20 +62,20 @@ describe('handleClassNamesChange', () => {
it('handles different nodes', () => {
const fooAdd = sandbox.spy()
const fooRemove = sandbox.spy()
- const fooComponents = new Set([FooComponent])
- const fooNodeRef = createNodeRefMock(fooAdd, fooRemove)
+ const fooNode = createNodeMock(fooAdd, fooRemove)
+ const fooRefs = new Set([fooRef])
const barAdd = sandbox.spy()
const barRemove = sandbox.spy()
- const barComponents = new Set([BarComponent])
- const barNodeRef = createNodeRefMock(barAdd, barRemove)
+ const barNode = createNodeMock(barAdd, barRemove)
+ const barRefs = new Set([barRef])
- handleClassNamesChange(fooNodeRef, fooComponents)
+ handleClassNamesChange(fooNode, fooRefs)
barAdd.should.have.not.been.called()
barRemove.should.have.not.been.called()
- fooNodeRef.reset()
+ fooNode.reset()
- handleClassNamesChange(barNodeRef, barComponents)
+ handleClassNamesChange(barNode, barRefs)
fooAdd.should.have.not.been.called()
fooRemove.should.have.not.been.called()
})
diff --git a/test/specs/lib/hooks/useClassNamesOnNode-test.js b/test/specs/lib/hooks/useClassNamesOnNode-test.js
new file mode 100644
index 0000000000..d8f67a895b
--- /dev/null
+++ b/test/specs/lib/hooks/useClassNamesOnNode-test.js
@@ -0,0 +1,60 @@
+import React from 'react'
+import useClassNamesOnNode from 'src/lib/hooks/useClassNamesOnNode'
+
+function TestComponent(props) {
+ useClassNamesOnNode(props.node, props.className)
+ return null
+}
+
+describe('useClassNamesOnNode', () => {
+ describe('node', () => {
+ it('will add className to specified node', () => {
+ const node = document.createElement('div')
+ mount()
+
+ node.classList.contains('foo').should.be.equal(true)
+ })
+
+ it('will update className on specified node', () => {
+ const node = document.createElement('div')
+ const wrapper = mount()
+
+ wrapper.setProps({ className: 'bar' })
+ node.classList.contains('foo').should.be.equal(false)
+ node.classList.contains('bar').should.be.equal(true)
+ })
+
+ it('will add multiple classNames', () => {
+ const node = document.createElement('div')
+
+ mount(
+ <>
+
+
+ >,
+ )
+
+ node.classList.contains('bar').should.be.equal(true)
+ node.classList.contains('bar').should.be.equal(true)
+ node.classList.contains('baz').should.be.equal(true)
+ })
+
+ it('will remove className on specified node', () => {
+ const node = document.createElement('div')
+ const wrapper = mount()
+
+ node.classList.contains('foo').should.be.equal(true)
+
+ wrapper.unmount()
+ node.classList.contains('foo').should.be.equal(false)
+ })
+
+ it('supports React ref objects', () => {
+ const nodeRef = React.createRef()
+ nodeRef.current = document.createElement('div')
+
+ mount()
+ nodeRef.current.classList.contains('foo').should.be.equal(true)
+ })
+ })
+})
diff --git a/test/specs/modules/Modal/Modal-test.js b/test/specs/modules/Modal/Modal-test.js
index 5d85f99220..4cabad83db 100644
--- a/test/specs/modules/Modal/Modal-test.js
+++ b/test/specs/modules/Modal/Modal-test.js
@@ -13,6 +13,7 @@ import {
assertNodeContains,
assertBodyClasses,
assertBodyContains,
+ assertWithTimeout,
domEvent,
sandbox,
} from 'test/utils'
@@ -538,15 +539,16 @@ describe('Modal', () => {
wrapperMount(foo)
window.innerHeight = 10
- requestAnimationFrame(() => {
- assertBodyClasses('scrolling')
- window.innerHeight = 10000
-
- requestAnimationFrame(() => {
- assertBodyClasses('scrolling', false)
- done()
- })
- })
+ assertWithTimeout(
+ () => {
+ assertBodyClasses('scrolling')
+ window.innerHeight = 10000
+ },
+ () =>
+ assertWithTimeout(() => {
+ assertBodyClasses('scrolling', false)
+ }, done),
+ )
})
it('adds the scrolling class to the body after re-open', (done) => {
@@ -555,18 +557,23 @@ describe('Modal', () => {
window.innerHeight = 10
wrapperMount(foo)
- requestAnimationFrame(() => {
- assertBodyClasses('scrolling')
- domEvent.click('.ui.dimmer')
-
- assertBodyClasses('scrolling', false)
-
- wrapper.setProps({ open: true })
- requestAnimationFrame(() => {
+ assertWithTimeout(
+ () => {
assertBodyClasses('scrolling')
- done()
- })
- })
+ domEvent.click('.ui.dimmer')
+ },
+ () =>
+ assertWithTimeout(
+ () => {
+ assertBodyClasses('scrolling', false)
+ wrapper.setProps({ open: true })
+ },
+ () =>
+ assertWithTimeout(() => {
+ assertBodyClasses('scrolling')
+ }, done),
+ ),
+ )
})
it('removes the scrolling class from the body on unmount', (done) => {
diff --git a/test/specs/modules/Modal/ModalDimmer-test.js b/test/specs/modules/Modal/ModalDimmer-test.js
index eb4b157abb..a304970947 100644
--- a/test/specs/modules/Modal/ModalDimmer-test.js
+++ b/test/specs/modules/Modal/ModalDimmer-test.js
@@ -12,24 +12,27 @@ describe('ModalDimmer', () => {
describe('children', () => {
it('adds classes to "MountNode"', () => {
- const wrapper = shallow()
+ const element = document.createElement('div')
+ mount()
- wrapper.find('MountNode').should.have.className('dimmable')
- wrapper.find('MountNode').should.have.className('dimmed')
+ element.className.should.contain('dimmable')
+ element.className.should.contain('dimmed')
})
})
describe('blurring', () => {
it('adds nothing "MountNode" by default', () => {
- shallow()
- .find('MountNode')
- .should.have.not.className('blurring')
+ const element = document.createElement('div')
+ mount()
+
+ element.className.should.not.contain('blurring')
})
it('adds a class to "MountNode" when is "true"', () => {
- shallow()
- .find('MountNode')
- .should.have.className('blurring')
+ const element = document.createElement('div')
+ mount()
+
+ element.className.should.contain('blurring')
})
})
@@ -47,27 +50,19 @@ describe('ModalDimmer', () => {
})
})
- describe('mountNode', () => {
- it('is passed to "MountNode" as "node"', () => {
- const mountNode = document.createElement('div')
-
- shallow()
- .find('MountNode')
- .should.have.prop('node', mountNode)
- })
- })
-
describe('scrolling', () => {
it('adds nothing "MountNode" by default', () => {
- shallow()
- .find('MountNode')
- .should.have.not.className('scrolling')
+ const element = document.createElement('div')
+ mount()
+
+ element.className.should.not.contain('scrolling')
})
it('adds "className" to "MountNode"', () => {
- shallow()
- .find('MountNode')
- .should.have.className('scrolling')
+ const element = document.createElement('div')
+ mount()
+
+ element.className.should.contain('scrolling')
})
})