test: add test

This commit is contained in:
infeng 2018-08-15 17:31:32 +08:00
parent 34fa4b4695
commit 79f691842b
8 changed files with 4256 additions and 42 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
node_modules/
dist/
lib/
.DS_STORE
.DS_STORE
coverage

3680
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"description": "react image viewer",
"main": "dist/index",
"scripts": {
"test": "npm run lint",
"test": "jest",
"start": "dora --port 8001 --plugins \"webpack,webpack-hmr,browser-history?index=/demo/index.html\"",
"lint": "tslint -c tslint.json src/**/*.ts src/**/*.tsx",
"build": "atool-build --config webpack.config.prop.js && gulp",
@ -32,11 +32,13 @@
"typings": "lib/index.d.ts",
"homepage": "https://github.com/infeng/react-viewer#readme",
"devDependencies": {
"@types/jest": "^23.3.1",
"@types/node": "^6.0.45",
"@types/react": "^0.14.39",
"@types/react-dom": "^0.14.17",
"antd": "^3.7.1",
"atool-build": "^1.0.2",
"babel-jest": "^23.4.2",
"babel-plugin-import": "^1.2.1",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-runtime": "^6.11.6",
@ -44,24 +46,63 @@
"dora-plugin-browser-history": "^0.2.0",
"dora-plugin-webpack": "^1.0.0",
"dora-plugin-webpack-hmr": "^0.2.1",
"enzyme": "^3.4.1",
"enzyme-adapter-react-16": "^1.2.0",
"enzyme-to-json": "^3.3.4",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-typescript": "^3.0.2",
"html-loader": "^0.4.4",
"html-webpack-plugin": "^2.22.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^23.5.0",
"jest-environment-jsdom": "^23.4.0",
"jest-environment-jsdom-global": "^1.1.0",
"jest-static-stubs": "0.0.1",
"merge2": "^1.0.2",
"pre-commit": "^1.1.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-test-render": "^1.1.1",
"through2": "^2.0.1",
"ts-jest": "^23.1.3",
"tslint": "^3.15.1",
"typescript": "^2.6.2",
"webpack": "^1.13.2"
"webpack": "^1.13.2",
"wolfy87-eventemitter": "^5.2.5"
},
"dependencies": {
"classnames": "^2.2.5"
},
"pre-commit": [
"lint"
]
],
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest",
"\\.js$": "babel-jest"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx"
],
"moduleNameMapper": {
"^.+\\.(jpg|jpeg)$": "jest-static-stubs/jpg",
"\\.(css|less)$": "identity-obj-proxy"
},
"moduleDirectories": [
"node_modules"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"globals": {
"ts-jest": {
"tsConfigFile": "tsconfig.test.json"
}
}
}
}

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import Loading from './Loading';
import classnames from 'classnames';
export interface ViewerCanvasProps {
prefixCls: string;
@ -130,10 +131,10 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
rotate(${this.props.rotate}deg) scaleX(${this.props.scaleX}) scaleY(${this.props.scaleY})`,
};
let imgClass = this.props.drag ? 'drag' : '';
if (!this.state.isMouseDown) {
imgClass += ` ${this.props.prefixCls}-image-transition`;
}
const imgClass = classnames(`${this.props.prefixCls}-image`, {
drag: this.props.drag,
[`${this.props.prefixCls}-image-transition`]: !this.state.isMouseDown,
});
let style = {
zIndex: this.props.zIndex,

View File

@ -140,7 +140,6 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
imgSrc = images[activeIndex].src;
}
let img = new Image();
img.src = imgSrc;
if (firstLoad) {
this.setState({
activeIndex: activeIndex,
@ -199,6 +198,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
loading: false,
});
};
img.src = imgSrc;
}
handleChangeImg = (newIndex: number) => {

View File

@ -92,6 +92,7 @@ export default class ViewerToolbar extends React.Component<ViewerToolbarProps, a
key={config.key}
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(config);}}
data-key={config.key}
>
{content}
</li>
@ -102,7 +103,9 @@ export default class ViewerToolbar extends React.Component<ViewerToolbarProps, a
let attributeNode = this.props.attribute ? (
<p className={`${this.props.prefixCls}-attribute`}>
{this.props.alt && `${this.props.alt}`}
{this.props.noImgDetails || `(${this.props.width} x ${this.props.height})`}
{this.props.noImgDetails || <span className={`${this.props.prefixCls}-img-details`}>
{`(${this.props.width} x ${this.props.height})`}
</span>}
</p>
) : null;
let toolbars = this.props.toolbars;

View File

@ -0,0 +1,533 @@
import Viewer from '../index';
import ViewerProps from '../ViewerProps';
import { configure, mount } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import * as React from 'react';
const img2 = require('../../demo/images/landscape2.jpg');
const img = require('../../demo/images/landscape.jpg');
configure({ adapter: new Adapter() });
function $$(className) {
return document.body.querySelectorAll(className);
}
interface ViewerTesterProps {
hasContainer?: boolean;
}
class ViewerTester extends React.Component<ViewerTesterProps & ViewerProps, any> {
static defaultProps = {
hasContainer: false,
};
container: any;
constructor(props) {
super(props);
this.state = {
visible: false,
activeIndex: 0,
};
}
handleOpen = () => {
this.setState({
visible: true,
});
}
handleChangeActiveIndex = () => {
this.setState({
activeIndex: 1,
});
}
render() {
let images = [{
src: img,
alt: 'lake',
downloadUrl: '',
}, {
src: img2,
alt: 'mountain',
downloadUrl: '',
}];
const { hasContainer, ...viewerProps } = this.props;
return (
<div>
<button id="viewer-tester-open-btn" onClick={this.handleOpen}>open viewer</button>
<button id="viewer-tester-change-btn" onClick={this.handleChangeActiveIndex}>change active index</button>
<div id="container" ref={ref => {this.container = ref;}} style={{ width: '150px', height: '150px' }}></div>
<Viewer
visible={this.state.visible}
images={images}
activeIndex={this.state.activeIndex}
container={hasContainer ? this.container : false}
ref="viewer"
onClose={() => { this.setState({ visible: false }); }}
{...viewerProps}
/>
</div>
);
}
}
import * as EventEmitter from 'wolfy87-eventemitter';
class MockImage {
source = '';
width = 0;
height = 0;
ee = new EventEmitter();
constructor() {
this.ee.defineEvents(['load', 'error']);
}
set src(value: string) {
this.source = value;
this.width = this.height = 100;
this.ee.emitEvent('load');
}
set onerror(ev) {
this.ee.addListener('error', ev);
}
set onload(ev) {
this.ee.addListener('load', ev);
}
addEventListener(event, callback) {
this.ee.addListener(event, callback);
}
get src() {
return this.source;
}
}
global.Image = MockImage;
function triggerMouseEvent (node, eventType, x = 0, y = 0) {
const clickEvent = new MouseEvent(eventType, {
clientX: x,
clientY: y,
view: window,
bubbles: true,
cancelable: true,
});
node.dispatchEvent (clickEvent);
}
function triggerWheel(node, eventType, deltaY) {
const wheelEvent = new WheelEvent(eventType, {
view: window,
bubbles: true,
cancelable: true,
deltaY,
});
node.dispatchEvent (wheelEvent);
}
function triggerKeyboard(node, eventType, keyCode, ctrlKey = false) {
const wheelEvent = new KeyboardEvent(eventType, {
view: window,
bubbles: true,
cancelable: true,
keyCode: keyCode,
ctrlKey,
});
node.dispatchEvent (wheelEvent);
}
function getTransformValue(transform) {
const translateXReg = /translateX\((.+)px\)(?= translateY)/;
const translateYReg = /translateY\((.+)px\)/;
const rotateReg = /rotate\((.+)deg\)/;
const scaleXReg = /scaleX\((.+)\) /;
const scaleYReg = /scaleY\((.+)\)/;
const translateX = transform.match(translateXReg)[1];
const translateY = transform.match(translateYReg)[1];
const rotate = transform.match(rotateReg)[1];
const scaleX = transform.match(scaleXReg)[1];
const scaleY = transform.match(scaleYReg)[1];
return {
translateX,
translateY,
rotate,
scaleX,
scaleY,
};
}
jest.useFakeTimers();
let wrapper = null;
interface ViewerHelperNewOptions extends ViewerProps, ViewerTesterProps {}
class ViewerHelper {
new(props: ViewerHelperNewOptions = {}) {
if (wrapper) {
wrapper.unmount();
}
wrapper = mount(<ViewerTester {...props} />);
}
open() {
wrapper.find('#viewer-tester-open-btn').simulate('click');
this.skipAnimation();
}
skipAnimation() {
jest.advanceTimersByTime(1000);
wrapper.ref('viewer').component.handleTransitionEnd();
}
}
const viewerHelper = new ViewerHelper();
describe('Viewer', () => {
it('open and close', () => {
viewerHelper.new();
viewerHelper.open();
expect($$('img.react-viewer-image')).toHaveLength(1);
$$('.react-viewer-close')[0].click();
viewerHelper.skipAnimation();
expect($$('.react-viewer')[0].style.display).toBe('none');
});
it('render with no footer', () => {
viewerHelper.new({ noFooter: true });
viewerHelper.open();
expect($$('.react-viewer-footer')).toHaveLength(0);
});
it('render with no navbar', () => {
viewerHelper.new({ noNavbar: true });
viewerHelper.open();
expect($$('.react-viewer-navbar')).toHaveLength(0);
});
it('render with no toolbar', () => {
viewerHelper.new({ noToolbar: true });
viewerHelper.open();
expect($$('.react-viewer-toolbar')).toHaveLength(0);
});
it('render with no attribute', () => {
viewerHelper.new({ attribute: false });
viewerHelper.open();
expect($$('.react-viewer-attribute')).toHaveLength(0);
});
it('render with no img details', () => {
viewerHelper.new({ noImgDetails: true });
viewerHelper.open();
expect($$('.react-viewer-img-details')).toHaveLength(0);
});
it('render with no zoom rotate scale change toolbar button', () => {
viewerHelper.new({
zoomable: false,
rotatable: false,
scalable: false,
changeable: false,
});
viewerHelper.open();
expect($$('.react-viewer-icon-zoomIn')).toHaveLength(0);
expect($$('.react-viewer-icon-zoomOut')).toHaveLength(0);
expect($$('.react-viewer-icon-rotateLeft')).toHaveLength(0);
expect($$('.react-viewer-icon-rotateRight')).toHaveLength(0);
expect($$('.react-viewer-icon-scaleX')).toHaveLength(0);
expect($$('.react-viewer-icon-scaleY')).toHaveLength(0);
expect($$('.react-viewer-icon-prev')).toHaveLength(0);
expect($$('.react-viewer-icon-next')).toHaveLength(0);
});
it('change active index success', () => {
viewerHelper.new();
viewerHelper.open();
wrapper.find('#viewer-tester-change-btn').simulate('click');
viewerHelper.skipAnimation();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('mountain');
});
it('custom toolbar', () => {
const handleClick = jest.fn();
viewerHelper.new({
customToolbar: toolbars => {
return toolbars.concat([{
key: 'test',
render: <div id="c">C</div>,
onClick: handleClick,
}]);
},
});
viewerHelper.open();
expect($$('li[data-key=test]')).toHaveLength(1);
$$('li[data-key=test]')[0].click();
expect(handleClick).toBeCalledWith(
expect.objectContaining({
alt: 'lake',
downloadUrl: expect.anything(),
src: expect.any(String),
}),
);
});
it('handle mask click', () => {
const handleMaskClick = jest.fn();
viewerHelper.new({
onMaskClick: handleMaskClick,
});
viewerHelper.open();
const canvas = $$('.react-viewer-canvas')[0];
triggerMouseEvent(canvas, 'mousedown');
expect(handleMaskClick).toBeCalledWith(expect.anything());
});
it('move image with mouse move', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
const oldTransform = imgNode.style.transform;
const canvas = $$('.react-viewer-canvas')[0];
triggerMouseEvent(canvas, 'mousedown');
triggerMouseEvent(canvas, 'mousemove', 50, 50);
const newTransform = imgNode.style.transform;
const oldTransformValue = getTransformValue(oldTransform);
const newTransformValue = getTransformValue(newTransform);
expect(newTransformValue.translateX - oldTransformValue.translateX).toBe(50);
expect(newTransformValue.translateY - oldTransformValue.translateY).toBe(50);
});
it('change active iamge whith prev and next button', () => {
viewerHelper.open();
viewerHelper.open();
$$('li[data-key=next]')[0].click();
$$('li[data-key=next]')[0].click();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('lake');
$$('li[data-key=prev]')[0].click();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('mountain');
});
it('rotate image', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
$$('li[data-key=rotateRight]')[0].click();
expect(getTransformValue(imgNode.style.transform).rotate).toBe('90');
$$('li[data-key=rotateLeft]')[0].click();
expect(getTransformValue(imgNode.style.transform).rotate).toBe('0');
});
it('scale image', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
$$('li[data-key=scaleX]')[0].click();
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('-1');
$$('li[data-key=scaleY]')[0].click();
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('-1');
});
it('zoom image', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
$$('li[data-key=zoomIn]')[0].click();
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1.05');
$$('li[data-key=zoomOut]')[0].click();
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1');
});
it('mouse wheel', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
const viewer = $$('.react-viewer')[0];
triggerWheel(viewer, 'wheel', -1);
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1.05');
triggerWheel(viewer, 'wheel', 1);
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1');
});
it('can not drag', () => {
viewerHelper.new({
drag: false,
});
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
const oldTransform = imgNode.style.transform;
const canvas = $$('.react-viewer-canvas')[0];
triggerMouseEvent(canvas, 'mousedown');
triggerMouseEvent(canvas, 'mousemove', 50, 50);
const newTransform = imgNode.style.transform;
const oldTransformValue = getTransformValue(oldTransform);
const newTransformValue = getTransformValue(newTransform);
expect(newTransformValue.translateX - oldTransformValue.translateX).toBe(0);
expect(newTransformValue.translateY - oldTransformValue.translateY).toBe(0);
});
it('change active image with nav', () => {
viewerHelper.new({});
viewerHelper.open();
const navList = $$('.react-viewer-list')[0];
navList.children[1].click();
viewerHelper.skipAnimation();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('mountain');
});
it('render witch container', () => {
viewerHelper.new({
hasContainer: true,
});
viewerHelper.open();
expect(wrapper.render().find('.react-viewer-inline')).toHaveLength(1);
wrapper.ref('viewer').component.handleMouseScroll(new WheelEvent('wheel', {
view: window,
bubbles: true,
cancelable: true,
deltaY: -1,
}));
expect(getTransformValue(wrapper.render().find('.react-viewer-image')[0].attribs.style).scaleX).toBe('1.05');
});
it('reset image', () => {
viewerHelper.new();
viewerHelper.open();
let imgNode = $$('img.react-viewer-image')[0];
const oldTransformValue = getTransformValue(imgNode.style.transform);
$$('li[data-key=scaleX]')[0].click();
$$('li[data-key=reset]')[0].click();
const newTransformValue = getTransformValue(imgNode.style.transform);
expect(oldTransformValue.scaleX - newTransformValue.scaleX).toBe(0);
});
it('download', () => {
viewerHelper.new({
downloadable: true,
});
viewerHelper.open();
$$('li[data-key=download]')[0].click();
});
it('keyboard support', () => {
viewerHelper.new();
viewerHelper.open();
// close
triggerKeyboard(document, 'keydown', 27);
viewerHelper.skipAnimation();
expect($$('.react-viewer')[0].style.display).toBe('none');
viewerHelper.open();
// prev
triggerKeyboard(document, 'keydown', 37);
viewerHelper.skipAnimation();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('mountain');
// next
triggerKeyboard(document, 'keydown', 39);
viewerHelper.skipAnimation();
expect($$('.react-viewer-attribute')[0].innerHTML).toContain('lake');
let imgNode = $$('img.react-viewer-image')[0];
// zoomIn
triggerKeyboard(document, 'keydown', 38);
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1.05');
// zoomOut
triggerKeyboard(document, 'keydown', 40);
expect(getTransformValue(imgNode.style.transform).scaleX).toBe('1');
// rotateLeft
triggerKeyboard(document, 'keydown', 37, true);
expect(getTransformValue(imgNode.style.transform).rotate).toBe('-90');
// rotateRight
triggerKeyboard(document, 'keydown', 39, true);
expect(getTransformValue(imgNode.style.transform).rotate).toBe('0');
// reset
triggerKeyboard(document, 'keydown', 39, true);
triggerKeyboard(document, 'keydown', 49, true);
viewerHelper.skipAnimation();
imgNode = $$('img.react-viewer-image')[0];
expect(getTransformValue(imgNode.style.transform).rotate).toBe('0');
});
});

19
tsconfig.test.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"removeComments": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"types": [
"react",
"react-dom",
"node"
]
},
"exclude": [
"node_modules"
]
}