Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

scrollable

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scrollable - npm Package Compare versions

Comparing version 1.0.1 to 1.0.2

src/__tests__/scroller-test.js

3

package.json
{
"name": "scrollable",
"version": "1.0.1",
"version": "1.0.2",
"description": "Components for layer composition and scrolling with React.js",

@@ -24,2 +24,3 @@ "main": "src/scrollable.js",

"browserify-istanbul": "^0.2.1",
"codeclimate-test-reporter": "^0.1.0",
"coveralls": "^2.11.2",

@@ -26,0 +27,0 @@ "hammerjs": "^2.0.4",

## React Scrollable
[![Join the chat at https://gitter.im/yahoo/scrollable](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/yahoo/scrollable?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![build status](https://travis-ci.org/yahoo/scrollable.svg)](https://travis-ci.org/yahoo/scrollable)
[![Coverage Status](https://coveralls.io/repos/yahoo/scrollable/badge.svg)](https://coveralls.io/r/yahoo/scrollable)
[![Test Coverage](https://codeclimate.com/github/yahoo/scrollable/badges/coverage.svg)](https://codeclimate.com/github/yahoo/scrollable/coverage)
[![Code Climate](https://codeclimate.com/github/yahoo/scrollable/badges/gpa.svg)](https://codeclimate.com/github/yahoo/scrollable)

@@ -5,0 +8,0 @@ #### A library that brings smooth scrolling interactions to modern mobile browsers.

@@ -102,6 +102,20 @@ /* Copyright 2015, Yahoo Inc.

it("Inserting text nodes", function () {
sut = React.render(
<RectCacheConsumer />,
div
);
sut.getDOMNode().appendChild(document.createTextNode(' text test node '));
expect(true, "make sure inserting the element won't throw");
});
it("won't update after unmount", function () {
var resizeCallback = jasmine.createSpy();
// Adding viewport below will only force the code to have coverage
// but testing the viewport resize is probably a lot of work, as the
// event would need to be simulates. This feels an overkill, but Pull
// requests accepted =)
sut = React.render(
<RectCacheConsumer onResize={resizeCallback} />,
<RectCacheConsumer viewport onResize={resizeCallback} />,
div

@@ -108,0 +122,0 @@ );

@@ -23,14 +23,18 @@ /* Copyright 2015, Yahoo Inc.

TestUtils.renderIntoDocument(
<ScrollItem />
<ScrollItem serverStyles={true} />
);
expect(console.warn).toHaveBeenCalled();
expect(console.warn.calls.count()).toEqual(2);
expect(console.warn.calls.first().args[0]).toMatch('was not specified');
expect(console.warn.calls.mostRecent().args[0]).toMatch('was not specified');
expect(console.warn.calls.count()).toEqual(3);
expect(console.warn.calls.argsFor(0)).toMatch('was not specified');
expect(console.warn.calls.argsFor(1)).toMatch('was not specified');
expect(console.warn.calls.argsFor(2)).toMatch('expected `function`');
});
it("Won't throw outside <Scroller>", function () {
TestUtils.renderIntoDocument(
<ScrollItem name="foo" scrollHandler={function(){}} />
);
function go() {
TestUtils.renderIntoDocument(
<ScrollItem name="foo" scrollHandler={function(){}} />
);
}
expect(go).not.toThrow();
});

@@ -72,2 +76,56 @@

describe('Server-side rendering', function() {
it("Render styles from serverStyles prop", function () {
var Scroller = MockScroller();
var wrapper = React.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={function(){
return {
height: '50px',
};
}}>
foo
</ScrollItem>
</Scroller>,
div
);
var sut = TestUtils.findRenderedDOMComponentWithClass(wrapper, 'scrollable-item');
expect(sut.props.style.height).toBe('50px');
});
it("Won't throw if serverStyles returns false", function () {
var Scroller = MockScroller();
function go() {
React.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() {
React.render(
<Scroller>
<ScrollItem name="foo" scrollHandler={function(){}} serverStyles={true}>
foo
</ScrollItem>
</Scroller>,
div
);
}
expect(go).not.toThrow();
});
});
describe('integration with <Scroller>', function() {

@@ -147,2 +205,59 @@

it("execute _prendingOperation that the parent might have setup", function () {
var Scroller = MockScroller();
var SuposedConsumer = React.createClass({
componentDidMount: function() {
this.refs.item._prendingOperation = function(){};
},
render: function() {
return (
<Scroller>
<ScrollItem ref="item" name="foo" scrollHandler={function(){}} />
</Scroller>
);
},
});
var consumer = React.render(
<SuposedConsumer />,
div
);
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem);
spyOn(sut, '_prendingOperation');
sut.componentDidMount();
expect(sut._prendingOperation).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 = React.render(
<SuposedConsumer />,
div
);
var parent = TestUtils.findRenderedComponentWithType(consumer, Scroller);
parent.onResize = function () {};
spyOn(parent, 'onResize');
consumer.setState({resizeItem: true});
expect(parent.onResize).toHaveBeenCalled();
});
});

@@ -149,0 +264,0 @@

@@ -8,2 +8,76 @@ /* exported RectCache */

/*
This mixin will add a `.rect` property to the consumer element and will do a "best effort"
to keep it updated, with the caveats stated below.
API:
----
`.rect` (Object): This property will have the same object signature as
DOMElement.getBoundingClientRect() or will be a direct instance returned by
the native method.
`.onResize` (optional hook event): The consumer might implement this method to get notified
of changes to `.rect` changes.
`onResize` (callback prop): Owners of component instances that implement RectCache can hook
into size changes to get notifications.
`viewport` (property): Owners can initialize component instances that implement RectCache
that will also update when window resizes or orientation changes.
Silly example, but with all API being used:
-------------------------------------------
var HaveRectCache = React.createClass({
mixins: [RectCache],
render: function () {
return (<div onClick={this.theClick}>
<img src="large1.jpg" />
<img src="large2.jpg" />
</div>);
},
theClick: function() {
alert(this.rect.height);
},
});
var Comp = React.createClass({
render: function () {
return (<div>
<HaveRectCache viewport ref="theElement" onResize={this.whenResized}>
</div>);
},
whenResized: function() {
// also called if window resizes or orientation change
alert('Comp knows theElement height changed to ' + this.refs.theElement.rect.height);
},
});
How it works and quirks:
------------------------
The `.rect` object will have width and hight consistent at all times, but other properties
might get outdated if elements are dynamically re-positioned by application logic.
After initialization, the mixin will bind to DOM events to do a best effort into a watching
for element size changes. Current browser APIs won't allow for perfect resize detection on
a DOM node level, besides hacky solutions that add extra DOM and watch for scroll events. One
famous library that uses this technique is
[CSS Element Queries](https://github.com/marcj/css-element-queries). Instead, RectCache will
poll for `.getBoundingClientRect()` on reasonable events like "DOMSubtreeModified/Inserted"
and image onLoad events.
Because some edge cases might still happen, specially when window resizes or viewport
orientation changes (on mobile devices), the viewport property will help the relevant elements
to keep updated.
The reason this algorithm is used instead of the more reliable hacky solutions mentioned above,
is because using React is already adding a lot of DOM predictability to changes, and this
library only aims to "close the gap", when doing something with DOM outside of React code.
*/
var initialRect = { left : 0, right : 0, top : 0, height : 0, bottom : 0, width : 0 };

@@ -13,8 +87,9 @@

rect: initialRect,
_node: null,
_updateRectCache: function() {
if(!this.isMounted()) {
return; // Edge case, this should not happen, maybe react bug?
if(!this._node) {
return;
}
var oldRect = this.rect;
var newRect = this.getDOMNode().getBoundingClientRect();
var newRect = this._node.getBoundingClientRect();
this.rect = newRect;

@@ -33,8 +108,14 @@

_bindImgLoad: null,
componentDidMount: function(){
var node = this.getDOMNode();
var update = this._updateRectCache;
this._node = node;
this._bindImgLoad = function(event) {
watchLoadImages(event.target, update);
};
update();
getImageLoadedNotifications(node, update);
watchLoadImages(node, update);
node.addEventListener('DOMSubtreeModified', update);
node.addEventListener('DOMNodeInserted', this._bindImgLoad);
if (this.props.hasOwnProperty('viewport')) {

@@ -47,8 +128,11 @@ window.addEventListener('orientationchange', update);

componentWillUnmount: function(){
var node = this.getDOMNode();
var node = this._node;
var update = this._updateRectCache;
node.removeEventListener('DOMSubtreeModified', update);
node.removeEventListener('DOMNodeInserted', this._bindImgLoad);
this._node = null;
this._bindImgLoad = null;
if (this.props.hasOwnProperty('viewport')) {
window.removeEventListener('orientationchange', update);
window.removeEventListener("resize", update);
window.removeEventListener('orientationchange', update);
window.removeEventListener("resize", update);
}

@@ -58,18 +142,15 @@ },

function getImageLoadedNotifications(node, callback) {
watchLoadImages(node.getElementsByTagName('img'), callback);
node.addEventListener('DOMNodeInserted', function(event) {
var images = event.target.getElementsByTagName && event.target.getElementsByTagName('img');
if (event.target.nodeName.toLowerCase() === 'img') {
watchLoadImages([event.target], callback);
}
watchLoadImages(images, callback);
});
function watchLoadImages(node, callback) {
var imgArr = getAllImagesInTree(node);
for (var i = 0; i < imgArr.length; i++) {
var img = imgArr[i];
img.addEventListener('load', callback);
}
}
function watchLoadImages(imgArr, callback) {
if(imgArr && imgArr.length) {
for (var i = 0; i < imgArr.length; i++) {
var img = imgArr[i];
img.addEventListener('load', callback);
}
function getAllImagesInTree(node) {
if (node && node.nodeName.toLowerCase() === 'img') {
return [node];
} else {
return (node.getElementsByTagName && node.getElementsByTagName('img')) || [];
}

@@ -76,0 +157,0 @@ }

@@ -17,2 +17,3 @@ /* Copyright 2015, Yahoo Inc.

scrollHandler: React.PropTypes.func.isRequired,
serverStyles: React.PropTypes.func,
},

@@ -41,8 +42,3 @@

this._node = this.getDOMNode();
var styleObject = this._pendingStyles;
if (styleObject) {
for(var prop in styleObject) {
this._node.style[prop] = styleObject[prop];
}
}
this._prendingOperation && this._prendingOperation();
},

@@ -61,3 +57,6 @@

if (ssStyles) {
var styleObject = ssStyles(this, this._scrollingParent);
var styleObject;
try {
styleObject = ssStyles(this, this._scrollingParent);
} catch(e) {}
if (styleObject) {

@@ -64,0 +63,0 @@ styleObject = StyleHelper.scrollStyles(styleObject);

@@ -12,2 +12,4 @@ /* Copyright 2015, Yahoo Inc.

var ScrollerEvents;
/* istanbul ignore else */
if (inBrowser) {

@@ -79,15 +81,6 @@ ScrollerEvents = require('./scroller-events');

// Using styles directly and simple for loops yeilds HUGE performance
// improvements specially on iPhone 4 with iOS 7.
// Set styles
if (item._node) {
for(var prop in styleObject) {
if (!item._prevStyles || item._prevStyles[prop] !== styleObject[prop]) {
item._node.style[prop] = styleObject[prop];
}
}
item._prevStyles = styleObject;
applyStyles(item, styleObject);
} else {
item._pendingStyles = styleObject;
item._prendingOperation = queueStylesOperation(item, styleObject);
}

@@ -291,2 +284,5 @@

_getContentSize: function() {
if (!this.props.getContentSize) {
return {width: 0, height: 0};
}
return this.props.getContentSize(this._scrollItems, this);

@@ -388,2 +384,17 @@ },

function queueStylesOperation(item, styleObject) {
return applyStyles.bind(null, item, styleObject);
}
function applyStyles(item, styleObject) {
// Using styles directly and simple for loops yeilds HUGE performance
// improvements specially on iPhone 4 with iOS 7.
for(var prop in styleObject) {
if (!item._prevStyles || item._prevStyles[prop] !== styleObject[prop]) {
item._node.style[prop] = styleObject[prop];
}
}
item._prevStyles = styleObject;
}
module.exports = Scroller;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc