diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..dc20b71 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,11 @@ +const base = require('@umijs/fabric/dist/eslint'); + +module.exports = { + ...base, + rules: { + ...base.rules, + 'react/no-find-dom-node': 0, + 'jsx-a11y/label-has-associated-control': 0, + 'jsx-a11y/label-has-for': 0, + }, +}; diff --git a/.fatherrc.js b/.fatherrc.js new file mode 100644 index 0000000..c221753 --- /dev/null +++ b/.fatherrc.js @@ -0,0 +1,8 @@ +export default { + cjs: 'babel', + esm: { type: 'babel', importLibToEs: true }, + preCommit: { + eslint: true, + prettier: true, + }, +}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae7ed18..f28aa39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.storybook *.iml *.log .idea/ diff --git a/.travis.yml b/.travis.yml index ef38dfb..38bbfe1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,17 @@ language: node_js -sudo: false - -notifications: - email: - - yiminghe@gmail.com - node_js: -- 6.10.0 + - 10 -before_install: -- | - if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' - then - echo "Only docs were updated, stopping build process." - exit - fi - phantomjs --version script: - | if [ "$TEST_TYPE" = test ]; then - npm test + npm run coverage && \ + bash <(curl -s https://codecov.io/bash) else npm run $TEST_TYPE fi env: matrix: - TEST_TYPE=lint - - TEST_TYPE=test - - TEST_TYPE=coverage + - TEST_TYPE=test \ No newline at end of file diff --git a/README.md b/README.md index eb64e42..334a0e4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ React Align Component. Wrapper around https://github.com/yiminghe/dom-align. [![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] -[![Test coverage][coveralls-image]][coveralls-url] +[![Codecov][codecov-image]][codecov-url] [![gemnasium deps][gemnasium-image]][gemnasium-url] [![node version][node-image]][node-url] [![npm download][download-image]][download-url] @@ -14,8 +14,8 @@ React Align Component. Wrapper around https://github.com/yiminghe/dom-align. [npm-url]: http://npmjs.org/package/rc-align [travis-image]: https://img.shields.io/travis/react-component/align.svg?style=flat-square [travis-url]: https://travis-ci.org/react-component/align -[coveralls-image]: https://img.shields.io/coveralls/react-component/align.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/react-component/align?branch=master +[codecov-image]: https://img.shields.io/codecov/c/github/react-component/align/master.svg?style=flat-square +[codecov-url]: https://codecov.io/gh/react-component/align/branch/master [gemnasium-image]: http://img.shields.io/gemnasium/react-component/align.svg?style=flat-square [gemnasium-url]: https://gemnasium.com/react-component/align [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square diff --git a/examples/point.html b/examples/point.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/point.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/point.js b/examples/point.js index 6988766..ed5b1da 100644 --- a/examples/point.js +++ b/examples/point.js @@ -1,6 +1,5 @@ -import Align from 'rc-align'; import React, { Component } from 'react'; -import ReactDOM from 'react-dom'; +import Align from '../src'; const align = { points: ['cc', 'cc'], @@ -12,8 +11,8 @@ class Demo extends Component { }; onClick = ({ pageX, pageY }) => { - this.setState({ point: { pageX, pageY }}); - } + this.setState({ point: { pageX, pageY } }); + }; render() { return ( @@ -25,19 +24,22 @@ class Demo extends Component { Click this region please : ) - +
Align
+ style={{ + position: 'absolute', + width: 100, + height: 100, + background: 'rgba(0, 255, 0, 0.5)', + pointerEvents: 'none', + }} + > + Align +
); } } -ReactDOM.render(, document.getElementById('__react-content')); - +export default Demo; diff --git a/examples/simple.html b/examples/simple.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/simple.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/simple.js b/examples/simple.js index 651b9bc..3df36ec 100644 --- a/examples/simple.js +++ b/examples/simple.js @@ -1,6 +1,5 @@ -import Align from 'rc-align'; import React, { Component } from 'react'; -import ReactDOM from 'react-dom'; +import Align from '../src'; class Test extends Component { state = { @@ -16,11 +15,14 @@ class Test extends Component { this.id = setInterval(() => { const { random } = this.state; if (random) { - this.setState({ - randomWidth: 60 + 40 * Math.random(), - }, () => { - this.forceAlign(); - }); + this.setState( + { + randomWidth: 60 + 40 * Math.random(), + }, + () => { + this.forceAlign(); + }, + ); } }, 1000); } @@ -35,31 +37,31 @@ class Test extends Component { this.$container = document.getElementById('container'); } return this.$container; - } + }; - containerRef = (ele) => { + containerRef = ele => { this.$container = ele; - } + }; - alignRef = (node) => { + alignRef = node => { this.$align = node; - } + }; toggleMonitor = () => { - this.setState({ - monitor: !this.state.monitor, - }); - } + this.setState(({ monitor }) => ({ + monitor: !monitor, + })); + }; toggleRandom = () => { - this.setState({ - random: !this.state.random, - }); - } + this.setState(({ random }) => ({ + random: !random, + })); + }; forceAlign = () => { this.$align.forceAlign(); - } + }; render() { const { random, randomWidth } = this.state; @@ -71,29 +73,32 @@ class Test extends Component { }} >

- +    

- +
@@ -122,4 +124,4 @@ class Test extends Component { } } -ReactDOM.render(, document.getElementById('__react-content')); +export default Test; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..61d0430 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], +}; \ No newline at end of file diff --git a/now.json b/now.json new file mode 100644 index 0000000..15f6864 --- /dev/null +++ b/now.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "name": "rc-align", + "builds": [ + { + "src": "package.json", + "use": "@now/static-build", + "config": { "distDir": ".doc" } + } + ] +} diff --git a/package.json b/package.json index 45da6b8..5e600f8 100644 --- a/package.json +++ b/package.json @@ -24,39 +24,37 @@ "license": "MIT", "main": "./lib/index", "module": "./es/index", - "config": { - "port": 8100 - }, "scripts": { - "build": "rc-tools run build", - "compile": "rc-tools run compile --babel-runtime", - "gh-pages": "rc-tools run gh-pages", - "start": "rc-tools run server", - "pub": "rc-tools run pub --babel-runtime", - "lint": "rc-tools run lint", - "karma": "rc-test run karma", - "saucelabs": "rc-test run saucelabs", - "test": "rc-test run test", - "chrome-test": "rc-test run chrome-test", - "coverage": "rc-test run coverage" + "start": "cross-env NODE_ENV=development father doc dev --storybook", + "build": "father doc build --storybook", + "compile": "father build", + "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish", + "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js", + "test": "father test", + "coverage": "father test --coverage", + "now-build": "npm run build" }, - "devDependencies": { - "core-js": "^2.5.1", - "expect.js": "0.3.x", - "jquery": "^3.3.1", - "pre-commit": "1.x", - "rc-test": "6.x", - "rc-tools": "8.x", - "react": "^16.3.0", - "react-dom": "^16.3.0" + "peerDependencies": { + "react": "*", + "react-dom": "*" }, - "pre-commit": [ - "lint" - ], "dependencies": { - "babel-runtime": "^6.26.0", + "classnames": "2.x", "dom-align": "^1.7.0", - "prop-types": "^15.5.8", - "rc-util": "^4.0.4" + "rc-util": "^4.12.0", + "resize-observer-polyfill": "^1.5.1" + }, + "devDependencies": { + "@types/jest": "^24.0.18", + "@types/react": "^16.8.19", + "@types/react-dom": "^16.8.4", + "@types/warning": "^3.0.0", + "cross-env": "^6.0.0", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.14.0", + "enzyme-to-json": "^3.4.0", + "father": "^2.13.2", + "np": "^5.0.3", + "typescript": "^3.5.2" } } diff --git a/src/Align.jsx b/src/Align.jsx deleted file mode 100644 index 2305652..0000000 --- a/src/Align.jsx +++ /dev/null @@ -1,170 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ReactDOM from 'react-dom'; -import { alignElement, alignPoint } from 'dom-align'; -import addEventListener from 'rc-util/lib/Dom/addEventListener'; - -import { isWindow, buffer, isSamePoint, isSimilarValue, restoreFocus } from './util'; - -function getElement(func) { - if (typeof func !== 'function' || !func) return null; - return func(); -} - -function getPoint(point) { - if (typeof point !== 'object' || !point) return null; - return point; -} - -class Align extends Component { - static propTypes = { - childrenProps: PropTypes.object, - align: PropTypes.object.isRequired, - target: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ - clientX: PropTypes.number, - clientY: PropTypes.number, - pageX: PropTypes.number, - pageY: PropTypes.number, - }), - ]), - onAlign: PropTypes.func, - monitorBufferTime: PropTypes.number, - monitorWindowResize: PropTypes.bool, - disabled: PropTypes.bool, - children: PropTypes.any, - }; - - static defaultProps = { - target: () => window, - monitorBufferTime: 50, - monitorWindowResize: false, - disabled: false, - }; - - componentDidMount() { - const props = this.props; - // if parent ref not attached .... use document.getElementById - this.forceAlign(); - if (!props.disabled && props.monitorWindowResize) { - this.startMonitorWindowResize(); - } - } - - componentDidUpdate(prevProps) { - let reAlign = false; - const props = this.props; - - if (!props.disabled) { - const source = ReactDOM.findDOMNode(this); - const sourceRect = source ? source.getBoundingClientRect() : null; - - if (prevProps.disabled) { - reAlign = true; - } else { - const lastElement = getElement(prevProps.target); - const currentElement = getElement(props.target); - const lastPoint = getPoint(prevProps.target); - const currentPoint = getPoint(props.target); - - if (isWindow(lastElement) && isWindow(currentElement)) { - // Skip if is window - reAlign = false; - } else if ( - lastElement !== currentElement || // Element change - (lastElement && !currentElement && currentPoint) || // Change from element to point - (lastPoint && currentPoint && currentElement) || // Change from point to element - (currentPoint && !isSamePoint(lastPoint, currentPoint)) - ) { - reAlign = true; - } - - // If source element size changed - const preRect = this.sourceRect || {}; - if ( - !reAlign && - source && - (!isSimilarValue(preRect.width, sourceRect.width) || !isSimilarValue(preRect.height, sourceRect.height)) - ) { - reAlign = true; - } - } - - this.sourceRect = sourceRect; - } - - if (reAlign) { - this.forceAlign(); - } - - if (props.monitorWindowResize && !props.disabled) { - this.startMonitorWindowResize(); - } else { - this.stopMonitorWindowResize(); - } - } - - componentWillUnmount() { - this.stopMonitorWindowResize(); - } - - startMonitorWindowResize() { - if (!this.resizeHandler) { - this.bufferMonitor = buffer(this.forceAlign, this.props.monitorBufferTime); - this.resizeHandler = addEventListener(window, 'resize', this.bufferMonitor); - } - } - - stopMonitorWindowResize() { - if (this.resizeHandler) { - this.bufferMonitor.clear(); - this.resizeHandler.remove(); - this.resizeHandler = null; - } - } - - forceAlign = () => { - const { disabled, target, align, onAlign } = this.props; - if (!disabled && target) { - const source = ReactDOM.findDOMNode(this); - - let result; - const element = getElement(target); - const point = getPoint(target); - - // IE lose focus after element realign - // We should record activeElement and restore later - const activeElement = document.activeElement; - - if (element) { - result = alignElement(source, element, align); - } else if (point) { - result = alignPoint(source, point, align); - } - - restoreFocus(activeElement, source); - - if (onAlign) { - onAlign(source, result); - } - } - } - - render() { - const { childrenProps, children } = this.props; - const child = React.Children.only(children); - if (childrenProps) { - const newProps = {}; - const propList = Object.keys(childrenProps); - propList.forEach((prop) => { - newProps[prop] = this.props[childrenProps[prop]]; - }); - - return React.cloneElement(child, newProps); - } - return child; - } -} - -export default Align; diff --git a/src/Align.tsx b/src/Align.tsx new file mode 100644 index 0000000..1e0d872 --- /dev/null +++ b/src/Align.tsx @@ -0,0 +1,151 @@ +/** + * Removed props: + * - childrenProps + */ + +import React from 'react'; +import { composeRef } from 'rc-util/lib/ref'; +import findDOMNode from 'rc-util/lib/Dom/findDOMNode'; +import { alignElement, alignPoint } from 'dom-align'; +import addEventListener from 'rc-util/lib/Dom/addEventListener'; + +import { isSamePoint, restoreFocus, monitorResize } from './util'; +import { AlignType, AlignResult, TargetType, TargetPoint } from './interface'; +import useBuffer from './hooks/useBuffer'; + +export interface AlignProps { + align: AlignType; + target: TargetType; + onAlign: (source: HTMLElement, result: AlignResult) => void; + monitorBufferTime: number; + monitorWindowResize: boolean; + disabled: boolean; + children: React.ReactElement; +} + +interface MonitorRef { + element?: HTMLElement; + cancel: () => void; +} + +export interface RefAlign { + forceAlign: () => void; +} + +function getElement(func: TargetType) { + if (typeof func !== 'function') return null; + return func(); +} + +function getPoint(point: TargetType) { + if (typeof point !== 'object' || !point) return null; + return point; +} + +const Align: React.RefForwardingComponent = ( + { children, disabled, target, align, onAlign, monitorWindowResize, monitorBufferTime = 0 }, + ref, +) => { + const cacheRef = React.useRef<{ element?: HTMLElement; point?: TargetPoint }>({}); + const nodeRef = React.useRef(); + let childNode = React.Children.only(children); + + // ===================== Align ====================== + const forceAlignRef = React.useRef(); + + forceAlignRef.current = () => { + if (!disabled && target) { + const source = findDOMNode(nodeRef.current); + + let result: AlignResult; + const element = getElement(target); + const point = getPoint(target); + + cacheRef.current.element = element; + cacheRef.current.point = point; + + // IE lose focus after element realign + // We should record activeElement and restore later + const { activeElement } = document; + + if (element) { + result = alignElement(source, element, align); + } else if (point) { + result = alignPoint(source, point, align); + } + + restoreFocus(activeElement, source); + + if (onAlign) { + onAlign(source, result); + } + } + }; + + const [forceAlign, cancelForceAlign] = useBuffer(() => { + forceAlignRef.current(); + }, monitorBufferTime); + + // ===================== Effect ===================== + // Listen for target updated + const resizeMonitor = React.useRef({ + cancel: () => {}, + }); + React.useEffect(() => { + const element = getElement(target); + const point = getPoint(target); + + if (cacheRef.current.element !== element || !isSamePoint(cacheRef.current.point, point)) { + forceAlign(); + + // Add resize observer + if (resizeMonitor.current.element !== element) { + resizeMonitor.current.cancel(); + resizeMonitor.current.element = element; + resizeMonitor.current.cancel = monitorResize(element, forceAlign); + } + } + }); + + // Listen for window resize + const winResizeRef = React.useRef<{ remove: Function }>(null); + React.useEffect(() => { + if (monitorWindowResize) { + if (!winResizeRef.current) { + winResizeRef.current = addEventListener(window, 'resize', forceAlign); + } + } else if (winResizeRef.current) { + winResizeRef.current.remove(); + winResizeRef.current = null; + } + }, [monitorWindowResize]); + + // Clear all if unmount + React.useEffect( + () => () => { + resizeMonitor.current.cancel(); + if (winResizeRef.current) winResizeRef.current.remove(); + cancelForceAlign(); + }, + [], + ); + + // ====================== Ref ======================= + React.useImperativeHandle(ref, () => ({ + forceAlign, + })); + + // ===================== Render ===================== + if (React.isValidElement(childNode)) { + childNode = React.cloneElement(childNode, { + ref: composeRef((childNode as any).ref, nodeRef), + }); + } + + return childNode; +}; + +const RefAlign = React.forwardRef(Align); +RefAlign.displayName = 'Align'; + +export default RefAlign; diff --git a/src/hooks/useBuffer.tsx b/src/hooks/useBuffer.tsx new file mode 100644 index 0000000..2e925e7 --- /dev/null +++ b/src/hooks/useBuffer.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +export default (callback: Function, buffer: number) => { + const calledRef = React.useRef(false); + const timeoutRef = React.useRef(null); + + function cancelTrigger() { + window.clearTimeout(timeoutRef.current); + } + + function trigger() { + if (!calledRef.current) { + callback(); + calledRef.current = true; + + cancelTrigger(); + timeoutRef.current = window.setTimeout(() => { + calledRef.current = false; + }, buffer); + } else { + cancelTrigger(); + timeoutRef.current = window.setTimeout(() => { + calledRef.current = false; + trigger(); + }, buffer); + } + } + + return [trigger, cancelTrigger]; +}; diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..9e41d10 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,84 @@ +export type AlignPoint = + | 'tt' + | 'tb' + | 'tc' + | 'tl' + | 'tr' + | 'bt' + | 'bb' + | 'bc' + | 'bl' + | 'br' + | 'ct' + | 'cb' + | 'cc' + | 'cl' + | 'cr' + | 'lt' + | 'lb' + | 'lc' + | 'll' + | 'lr' + | 'rt' + | 'rb' + | 'rc' + | 'rl' + | 'rr'; + +export interface AlignType { + /** + * move point of source node to align with point of target node. + * Such as ['tr','cc'], align top right point of source node with center point of target node. + * Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */ + points?: AlignPoint[]; + /** + * offset source node by offset[0] in x and offset[1] in y. + * If offset contains percentage string value, it is relative to sourceNode region. + */ + offset?: number[]; + /** + * offset target node by offset[0] in x and offset[1] in y. + * If targetOffset contains percentage string value, it is relative to targetNode region. + */ + targetOffset?: number[]; + /** + * If adjustX field is true, will adjust source node in x direction if source node is invisible. + * If adjustY field is true, will adjust source node in y direction if source node is invisible. + */ + overflow?: { + adjustX?: boolean | number; + adjustY?: boolean | number; + }; + /** + * Whether use css right instead of left to position + */ + useCssRight?: boolean; + /** + * Whether use css bottom instead of top to position + */ + useCssBottom?: boolean; + /** + * Whether use css transform instead of left/top/right/bottom to position if browser supports. + * Defaults to false. + */ + useCssTransform?: boolean; +} + +export interface AlignResult { + points: AlignPoint[]; + offset: number[]; + targetOffset: number[]; + overflow: { + adjustX: boolean | number; + adjustY: boolean | number; + }; +} + +export interface TargetPoint { + clientX?: number; + clientY?: number; + pageX?: number; + pageY?: number; +} + +export type TargetType = (() => HTMLElement) | TargetPoint; diff --git a/src/util.js b/src/util.js deleted file mode 100644 index 99f5439..0000000 --- a/src/util.js +++ /dev/null @@ -1,56 +0,0 @@ -import contains from 'rc-util/lib/Dom/contains'; - -export function buffer(fn, ms) { - let timer; - - function clear() { - if (timer) { - clearTimeout(timer); - timer = null; - } - } - - function bufferFn() { - clear(); - timer = setTimeout(fn, ms); - } - - bufferFn.clear = clear; - - return bufferFn; -} - -export function isSamePoint(prev, next) { - if (prev === next) return true; - if (!prev || !next) return false; - - if ('pageX' in next && 'pageY' in next) { - return prev.pageX === next.pageX && prev.pageY === next.pageY; - } - - if ('clientX' in next && 'clientY' in next) { - return prev.clientX === next.clientX && prev.clientY === next.clientY; - } - - return false; -} - -export function isWindow(obj) { - return obj && typeof obj === 'object' && obj.window === obj; -} - -export function isSimilarValue(val1, val2) { - const int1 = Math.floor(val1); - const int2 = Math.floor(val2); - return Math.abs(int1 - int2) <= 1; -} - -export function restoreFocus(activeElement, container) { - // Focus back if is in the container - if ( - activeElement !== document.activeElement && - contains(container, activeElement) - ) { - activeElement.focus(); - } -} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..cd22388 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,52 @@ +import ResizeObserver from 'resize-observer-polyfill'; +import contains from 'rc-util/lib/Dom/contains'; +import { TargetPoint } from './interface'; + +export function isSamePoint(prev: TargetPoint, next: TargetPoint) { + if (prev === next) return true; + if (!prev || !next) return false; + + if ('pageX' in next && 'pageY' in next) { + return prev.pageX === next.pageX && prev.pageY === next.pageY; + } + + if ('clientX' in next && 'clientY' in next) { + return prev.clientX === next.clientX && prev.clientY === next.clientY; + } + + return false; +} + +export function restoreFocus(activeElement, container) { + // Focus back if is in the container + if (activeElement !== document.activeElement && contains(container, activeElement)) { + activeElement.focus(); + } +} + +export function monitorResize(element: HTMLElement, callback: Function) { + let prevWidth: number = null; + let prevHeight: number = null; + + function onResize([{ target }]: ResizeObserverEntry[]) { + const { width, height } = target.getBoundingClientRect(); + const fixedWidth = Math.floor(width); + const fixedHeight = Math.floor(height); + + if (prevWidth !== fixedWidth || prevHeight !== fixedHeight) { + callback({ width: fixedWidth, height: fixedHeight }); + } + + prevWidth = fixedWidth; + prevHeight = fixedHeight; + } + + const resizeObserver = new ResizeObserver(onResize); + if (element) { + resizeObserver.observe(element); + } + + return () => { + resizeObserver.disconnect(); + }; +} diff --git a/tests/element.spec.js b/tests/element.spec.js deleted file mode 100644 index 9b1fdac..0000000 --- a/tests/element.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint react/no-render-return-value:0 */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import $ from 'jquery'; -import expect from 'expect.js'; -import Align from '../src'; - -describe('point align', () => { - let container; - - beforeEach(() => { - container = $('
').appendTo(document.body); - }); - - afterEach(() => { - try { - ReactDOM.unmountComponentAtNode(container[0]); - } catch (e) { - // do nothing... - } - if (container) container.remove(); - container = null; - }); - - it('resize', () => { - const align = { - points: ['bc', 'tc'], - }; - - class Test extends React.Component { - state = { - height: 80, - }; - - getTarget = () => this.$target; - - targetRef = (ele) => { - this.$target = ele; - }; - - render() { - const { height } = this.state; - - return ( -
-
- target -
- -
- source -
-
-
- ); - } - } - - const instance = ReactDOM.render(, container[0]); - const $src = $('#ele_src'); - expect($src.offset().top).to.be(20); - - instance.setState({ - height: 60, - }); - expect($src.offset().top).to.be(40); - }); -}); diff --git a/tests/element.test.js b/tests/element.test.js new file mode 100644 index 0000000..813e1e8 --- /dev/null +++ b/tests/element.test.js @@ -0,0 +1,69 @@ +/* eslint-disable class-methods-use-this */ +import React from 'react'; +import { mount } from 'enzyme'; +import Align from '../src'; + +describe('element align', () => { + it('resize', () => { + jest.useFakeTimers(); + + const align = { + points: ['bc', 'tc'], + }; + + const onAlign = jest.fn(); + + class Test extends React.Component { + getTarget = () => this.$target; + + targetRef = ele => { + this.$target = ele; + }; + + render() { + return ( +
+
+ target +
+ +
+ source +
+
+
+ ); + } + } + + const wrapper = mount(); + expect(onAlign).toHaveBeenCalled(); + + // Window resize + onAlign.mockReset(); + window.dispatchEvent(new Event('resize')); + jest.runAllTimers(); + expect(onAlign).toHaveBeenCalled(); + + // Not listen resize + onAlign.mockReset(); + wrapper.setProps({ monitorWindowResize: false }); + window.dispatchEvent(new Event('resize')); + jest.runAllTimers(); + expect(onAlign).not.toHaveBeenCalled(); + + // Remove should not crash + wrapper.setProps({ monitorWindowResize: true }); + wrapper.unmount(); + + jest.useRealTimers(); + }); +}); +/* eslint-enable */ diff --git a/tests/index.js b/tests/index.js deleted file mode 100644 index 09268cf..0000000 --- a/tests/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import 'core-js/es6/map'; -import 'core-js/es6/set'; -import $ from 'jquery'; -import Align from '../src'; - -$('') - .appendTo(document.getElementsByTagName('head')); - -describe('rc-align', () => { - it('exists', () => { - expect(Align).to.be.ok(); - }); -}); - -require('./element.spec'); -require('./point.spec'); -require('./util.spec'); diff --git a/tests/point.spec.js b/tests/point.spec.js deleted file mode 100644 index 54f72cf..0000000 --- a/tests/point.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint react/no-render-return-value:0 */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import $ from 'jquery'; -import expect from 'expect.js'; -import Align from '../src'; - -describe('point align', () => { - let container; - - function createAlign(props, callback) { - class Test extends React.Component { - state = {}; - - render() { - return ( - -
- - ); - } - } - - return ReactDOM.render(, container[0], callback); - } - - beforeEach(() => { - container = $('
').appendTo(document.body); - }); - - afterEach(() => { - try { - ReactDOM.unmountComponentAtNode(container[0]); - } catch (e) { - // do nothing... - } - if (container) container.remove(); - container = null; - }); - - it('not pass point', (done) => { - createAlign( - { - align: { points: ['cc'] }, - target: null, - }, - () => { - const $align = $('#align')[0]; - expect($align).to.be.ok(); - expect($align.style.left).to.not.be.ok(); - expect($align.style.top).to.not.be.ok(); - done(); - }, - ); - }); - - it('pass point', (done) => { - const wrapper = createAlign( - { - align: { points: ['tc'] }, - target: null, - }, - ); - - wrapper.setState({ - target: { pageX: 1128, pageY: 903 }, - }, () => { - const $align = $('#align'); - expect($align[0]).to.be.ok(); - expect($align.offset().left).to.be(1118); - expect($align.offset().top).to.be(903); - - wrapper.setState({ - target: { pageX: 321, pageY: 613 }, - }, () => { - expect($align.offset().left).to.be(311); - expect($align.offset().top).to.be(613); - - done(); - }); - }); - }); -}); diff --git a/tests/point.test.js b/tests/point.test.js new file mode 100644 index 0000000..3bf020a --- /dev/null +++ b/tests/point.test.js @@ -0,0 +1,45 @@ +/* eslint-disable class-methods-use-this */ +import React from 'react'; +import { mount } from 'enzyme'; +import Align from '../src'; + +describe('point align', () => { + function createAlign(props) { + return mount( + +
+ , + ); + } + + it('not pass point', () => { + const onAlign = jest.fn(); + + createAlign({ + align: { points: ['cc'] }, + target: null, + onAlign, + }); + + expect(onAlign).not.toHaveBeenCalled(); + }); + + it('pass point', () => { + jest.useFakeTimers(); + const onAlign = jest.fn(); + const wrapper = createAlign({ + align: { points: ['tc'] }, + target: null, + onAlign, + }); + + expect(onAlign).not.toHaveBeenCalled(); + + wrapper.setProps({ target: { pageX: 1128, pageY: 903 } }); + jest.runAllTimers(); + expect(onAlign).toHaveBeenCalled(); + + jest.useRealTimers(); + }); +}); +/* eslint-enable */ diff --git a/tests/util.spec.js b/tests/util.test.js similarity index 54% rename from tests/util.spec.js rename to tests/util.test.js index 09ab476..269a0f9 100644 --- a/tests/util.spec.js +++ b/tests/util.test.js @@ -1,6 +1,3 @@ -/* eslint react/no-render-return-value:0 */ - -import expect from 'expect.js'; import { isSamePoint } from '../src/util'; describe('util', () => { @@ -10,14 +7,14 @@ describe('util', () => { isSamePoint( { pageX: 1, pageY: 2, clientX: 3, clientY: 4 }, { pageX: 1, pageY: 2, clientX: 1, clientY: 5 }, - ) - ).to.be.ok(); + ), + ).toBeTruthy(); expect( isSamePoint( { pageX: 1, pageY: 2, clientX: 3, clientY: 4 }, { pageX: 5, pageY: 6, clientX: 3, clientY: 4 }, - ) - ).not.to.be.ok(); + ), + ).toBeFalsy(); }); it('by client', () => { @@ -25,14 +22,19 @@ describe('util', () => { isSamePoint( { pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, { pageY: 2, clientX: 3, clientY: 4 }, - ) - ).to.be.ok(); + ), + ).toBeTruthy(); expect( - isSamePoint( - { pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, - { clientX: 5, clientY: 4 }, - ) - ).not.to.be.ok(); + isSamePoint({ pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, { clientX: 5, clientY: 4 }), + ).toBeFalsy(); + }); + + it('null should be false', () => { + expect(isSamePoint({ pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, null)).toBeFalsy(); + expect(isSamePoint(null, { pageX: 0, pageY: 2, clientX: 3, clientY: 4 })).toBeFalsy(); + }); + it('2 empty should be false', () => { + expect(isSamePoint({}, {})).toBeFalsy(); }); }); });