src/__tests__/scroll-item-test.js
/* Copyright 2015, Yahoo Inc.
Copyrights licensed under the MIT License.
See the accompanying LICENSE file for terms. */
"use strict";
var React = require('react');
var ReactDOM = require('react-dom');
var TestUtils = require('react-addons-test-utils');
var ScrollItem = require('../scroll-item');
var StyleHelper = require('../style-helper');
var ReactDOMServer = require('react-dom/server');
describe('<ScrollItem>', function() {
var div;
beforeEach(function() {
div = document.createElement('div');
document.body.appendChild(div);
});
describe('startup', function() {
it("required props", function () {
spyOn(console, 'error');
TestUtils.renderIntoDocument(
<ScrollItem serverStyles={true} />
);
expect(console.error).toHaveBeenCalled();
expect(console.error.calls.count()).toEqual(3);
expect(console.error.calls.argsFor(0)).toMatch('was not specified');
expect(console.error.calls.argsFor(1)).toMatch('was not specified');
expect(console.error.calls.argsFor(2)).toMatch('expected `function`');
});
it("Won't throw outside <Scroller>", function () {
function go() {
TestUtils.renderIntoDocument(
<ScrollItem name="foo" scrollHandler={function(){}} />
);
}
expect(go).not.toThrow();
});
it("implements RectCache mixin", function () {
var Scroller = MockScroller();
var wrapper = ReactDOM.render(
<Scroller>
<style>{".scrollable-item {float:left;} /* this will force it not to have width: auto; */ "}</style>
<ScrollItem name="foo" scrollHandler={function(){}}>
<div style={{width:200,height:200}} />
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedComponentWithType(wrapper, ScrollItem);
expect(sut.rect.height).toEqual(200);
expect(sut.rect.width).toEqual(200);
});
});
describe('Server-side rendering', function() {
it("Render styles from serverStyles prop", function () {
var Scroller = MockScroller();
var wrapper = ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={function(){
return {
height: '50px',
};
}}>
foo
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedDOMComponentWithClass(wrapper, 'scrollable-item');
var sutDOM = ReactDOM.findDOMNode(sut);
expect(sutDOM.style.height).toBe('50px');
});
it("serverStyles have all prefixes", function () {
var Scroller = MockScroller();
var finalString = ReactDOMServer.renderToString(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={function(){
return {
y: 10,
};
}}>
foo
</ScrollItem>
</Scroller>,
div
);
expect(finalString).toContain('transform:translate3d(0px, 10px, 0px);-webkit-transform:translate3d(0px, 10px, 0px);-moz-transform:translate3d(0px, 10px, 0px);-o-transform:translate3d(0px, 10px, 0px);-ms-transform:translate3d(0px, 10px, 0px);');
});
it("Won't throw if serverStyles returns false", function () {
var Scroller = MockScroller();
function go() {
ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={function(){
return false;
}}>
foo
</ScrollItem>
</Scroller>,
div
);
}
expect(go).not.toThrow();
});
it("Won't throw if serverStyles is not a function", function () {
var Scroller = MockScroller();
function go() {
ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={true}>
foo
</ScrollItem>
</Scroller>,
div
);
}
expect(go).not.toThrow();
});
it("cleanup serverStyles after componentDidMount", function (done) {
var Scroller = MockScroller();
var wrapper = ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){
return {y:10};
}} serverStyles={function(){
return {
y: 10,
};
}}>
foo
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedDOMComponentWithClass(wrapper, 'scrollable-item');
var sutDOM = ReactDOM.findDOMNode(sut);
var clientStyles = StyleHelper.scrollStyles({y:10});
setTimeout(function(){
for(var prop in clientStyles) {
expect(sutDOM.style[prop]).toEqual(clientStyles[prop]);
}
expect(sutDOM.getAttribute('style')).toEqual(sutDOM.style.cssText); // this fails if there is prefixes leftovers
done();
},1);
});
it("cleanup serverStyles honors 'style' React Prop", function (done) {
var Scroller = MockScroller();
var wrapper = ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){
return {y:10};
}} serverStyles={function(){
return {
y: 10,
};
}}
style={{
height: 10
}}>
foo
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedDOMComponentWithClass(wrapper, 'scrollable-item');
var clientStyles = StyleHelper.scrollStyles({y:10});
setTimeout(function(){
for(var prop in clientStyles) {
expect(sut.style[prop]).toEqual(clientStyles[prop]);
}
expect(sut.style.height).toEqual('10px');
done();
},1);
});
it("cleanup serverStyles after componentDidUpdate", function (done) {
var Scroller = MockScroller();
function positions10() {return {y:10};}
function positions20() {return {y:20};}
var SuposedConsumer = React.createClass({
getInitialState: function() {return {changePositions:false};},
render: function() {
var handler = this.state.changePositions ? positions20 : positions10;
return (
<Scroller ref="wrapper">
<ScrollItem name="foo" scrollHandler={handler} serverStyles={handler}>
foo
</ScrollItem>
</Scroller>
);
},
});
var consumer = ReactDOM.render(
<SuposedConsumer />,
div
);
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem);
var sutDOM = ReactDOM.findDOMNode(sut);
// update state
var clientStyles = StyleHelper.scrollStyles({y:20});
sut._prevStyles = clientStyles; // this would be done by <Scroller>
consumer.setState({changePositions: true}, function() {
setTimeout(function(){
for(var prop in clientStyles) {
expect(sutDOM.style[prop]).toEqual(clientStyles[prop]);
}
expect(sutDOM.getAttribute('style')).toEqual(sutDOM.style.cssText);
done();
done();
},300);
});
});
});
describe('integration with <Scroller>', function() {
it("register against scrollingParent", function () {
var Scroller = MockScroller();
var wrapper = ReactDOM.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}}>
foo
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedComponentWithType(wrapper, ScrollItem);
var _registerItemSpy = wrapper._registerItem.__reactBoundMethod;
expect(_registerItemSpy).toHaveBeenCalled();
expect(_registerItemSpy.calls.mostRecent().args[0]).toEqual(sut);
});
it("unregister from scrollingParent when unmounted", function () {
var Scroller = MockScroller();
var SuposedConsumer = React.createClass({
getInitialState: function() {return {remove:false};},
render: function() {
return (
<Scroller ref="wrapper">
{ !this.state.remove &&
<ScrollItem name="foo" scrollHandler={function(){}} />
}
</Scroller>
);
},
});
var consumer = ReactDOM.render(
<SuposedConsumer />,
div
);
// store instance before it's removed
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem);
// force instance to be unMounted
consumer.setState({remove: true});
// all good
var _unRegisterItemSpy = consumer.refs.wrapper._unRegisterItem.__reactBoundMethod;
expect(_unRegisterItemSpy).toHaveBeenCalled();
expect(_unRegisterItemSpy.calls.mostRecent().args[0]).toEqual(sut);
});
it("cache and cleanup _node for scrollingParent use", function () {
var Scroller = MockScroller();
var SuposedConsumer = React.createClass({
getInitialState: function() {return {remove:false};},
render: function() {
return (
<Scroller ref="wrapper">
{ !this.state.remove &&
<ScrollItem name="foo" scrollHandler={function(){}} />
}
</Scroller>
);
},
});
var consumer = ReactDOM.render(
<SuposedConsumer />,
div
);
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem);
var sutDOM = ReactDOM.findDOMNode(sut);
expect(sut._node).toEqual(sutDOM);
consumer.setState({remove: true});
expect(sut._node).toBe(null);
});
it("execute _pendingOperation that the parent might have setup", function () {
var Scroller = MockScroller();
var SuposedConsumer = React.createClass({
componentDidMount: function() {
this.refs.item._pendingOperation = function(){};
},
render: function() {
return (
<Scroller>
<ScrollItem ref="item" name="foo" scrollHandler={function(){}} />
</Scroller>
);
},
});
var consumer = ReactDOM.render(
<SuposedConsumer />,
div
);
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem);
spyOn(sut, '_pendingOperation');
sut.componentDidMount();
expect(sut._pendingOperation).toHaveBeenCalled();
});
it("Calls parent onResize method if item resizes", function () {
var Scroller = MockScroller();
var SuposedConsumer = React.createClass({
getInitialState: function() {return {resizeItem:false};},
render: function() {
return (
<Scroller ref="wrapper">
<ScrollItem name="foo" scrollHandler={function(){}}>
<div style={{height:'20px', width:'20px'}} />
{ this.state.resizeItem &&
<div style={{height:'20px', width:'20px'}} />
}
</ScrollItem>
</Scroller>
);
},
});
var consumer = ReactDOM.render(
<SuposedConsumer />,
div
);
var parent = TestUtils.findRenderedComponentWithType(consumer, Scroller);
parent.onResize = function () {};
spyOn(parent, 'onResize');
consumer.setState({resizeItem: true});
expect(parent.onResize).toHaveBeenCalled();
});
});
});
function MockScroller(preventSpy) {
if (!preventSpy) {
spyOn(console, 'warn'); // silence further warnings
}
return React.createClass({
childContextTypes: {
scrollingParent: React.PropTypes.object,
},
getChildContext: function() {
return {
scrollingParent: this,
};
},
_registerItem: jasmine.createSpy(),
_unRegisterItem: jasmine.createSpy(),
render: function() {
return (
<div {...this.props}>
{this.props.children}
</div>
);
},
});
}