Skip to content

Commit

Permalink
Children accepted must be a single a DOM Element
Browse files Browse the repository at this point in the history
  • Loading branch information
rattrayalex committed Feb 9, 2017
1 parent be8ad60 commit bb5bea6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 72 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,9 @@ waypoint as a line across the page. Whenever that line crosses a
[boundary](#offsets-and-boundaries), then the `onEnter` or `onLeave` callbacks
will be called.

When children are passed, the waypoint's size will be determined by the
size of a div wrapping the contained children (or, with `noWrapper={true}`,
the size of the child element you pass). The `onEnter` callback will be called
when *any* part of the children is visible in the viewport. The `onLeave`
callback will be called when *all* children have exited the viewport.
If you do pass a child, it must be a single DOM Element (eg; a `<div>`).
The `onEnter` callback will be called when *any* part of the child is visible
in the viewport. The `onLeave` callback will be called when *all* of the child have exited the viewport.
(Note that this is measured only on a single axis; strangely positioned elements
may not work as expected).

Expand Down
80 changes: 20 additions & 60 deletions spec/waypoint_spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import ReactDOM from 'react-dom';
import Waypoint from '../src/waypoint.jsx';
Expand Down Expand Up @@ -343,7 +344,7 @@ describe('<Waypoint>', function() {
describe('with children', () => {
beforeEach(() => {
this.childrenHeight = 80;
this.props.children = [
this.props.children = React.createElement('div', {}, [
React.createElement('div', {
key: 1,
style: {
Expand All @@ -356,7 +357,7 @@ describe('<Waypoint>', function() {
height: this.childrenHeight / 2,
}
}),
];
]);
});

describe('when scrolling down far enough', () => {
Expand Down Expand Up @@ -853,68 +854,35 @@ describe('<Waypoint>', function() {
});
});

describe('when the Waypoint has children and is above the top', () => {
beforeEach(() => {
this.topSpacerHeight = 200;
this.bottomSpacerHeight = 200;
this.childrenHeight = 100;
this.props.children = React.createElement('div', {
style: {
height: this.childrenHeight,
}
});
this.scrollable = this.subject();
describe('when the Waypoint has children that are not DOM Elements', () => {
const errorMessage = 'You must wrap any Component Elements passed to Waypoint ' +
'in a DOM Element (eg; a <div>).';

// Because of how we detect when a Waypoint is scrolled past without any
// scroll event fired when it was visible, we need to reset callback
// spies.
scrollNodeTo(this.scrollable, 400);
this.props.onEnter.calls.reset();
this.props.onLeave.calls.reset();
scrollNodeTo(this.scrollable, 400);
});
it('errors with a stateless component', () => {
const StatelessComponent = () => React.createElement('div');
this.props.children = React.createElement(StatelessComponent);

it('does not call the onEnter handler', () => {
expect(this.props.onEnter).not.toHaveBeenCalled();
expect(this.subject).toThrowError(errorMessage);
});

it('does not call the onLeave handler', () => {
expect(this.props.onLeave).not.toHaveBeenCalled();
});

describe('when scrolled back up just past the bottom', () => {
beforeEach(() => {
scrollNodeTo(this.scrollable, this.topSpacerHeight + 50);
});

it('calls the onEnter handler', () => {
expect(this.props.onEnter).
toHaveBeenCalledWith({
currentPosition: Waypoint.inside,
previousPosition: Waypoint.above,
event: jasmine.any(Event),
waypointTop: -40,
waypointBottom: -40 + this.childrenHeight,
viewportTop: this.margin,
viewportBottom: this.margin + this.parentHeight,
});
});
it('errors with a class-based component', () => {
class ClassBasedComponent extends React.Component {
render() {
return React.createElement('div');
}
}
this.props.children = React.createElement(ClassBasedComponent);

it('does not call the onLeave handler', () => {
expect(this.props.onLeave).not.toHaveBeenCalled();
});
expect(this.subject).toThrowError(errorMessage);
});
});

describe('when noWrapper=true and child is above the top', () => {
describe('when the Waypoint has children and is above the top', () => {
beforeEach(() => {
this.topSpacerHeight = 200;
this.bottomSpacerHeight = 200;
this.childrenHeight = 100;
this.childRefSpy = jasmine.createSpy('ref');
this.props.noWrapper = true;
this.props.children = React.createElement('section', {
ref: this.childRefSpy,
this.props.children = React.createElement('div', {
style: {
height: this.childrenHeight,
}
Expand All @@ -930,14 +898,6 @@ describe('<Waypoint>', function() {
scrollNodeTo(this.scrollable, 400);
});

it('calls the original ref handler', () => {
expect(this.childRefSpy).toHaveBeenCalled();
});

it('does not have an extra div', () => {
expect(this.scrollable.children[1].nodeName).toBe('SECTION');
});

it('does not call the onEnter handler', () => {
expect(this.props.onEnter).not.toHaveBeenCalled();
});
Expand Down
21 changes: 14 additions & 7 deletions src/waypoint.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ function computeOffsetPixels(offset, contextHeight) {
}
}

/**
* @param {React.element} Component
* @return {bool} Whether the component is a DOM Element
*/
function isDomElement(Component) {
return (typeof Component.type === 'string');
}

/**
* Calls a function when you scroll to the element.
Expand Down Expand Up @@ -352,8 +359,13 @@ export default class Waypoint extends React.Component {
* @return {Object}
*/
render() {
if (this.props.noWrapper) {
if (this.props.children) {
const child = React.Children.only(this.props.children);
if (!isDomElement(child)) {
throw new Error(
'You must wrap any Component Elements passed to Waypoint in a DOM Element (eg; a <div>).'
);
}
const ref = (node) => {
this.refElement(node);
if (this.props.children.ref) {
Expand All @@ -363,19 +375,14 @@ export default class Waypoint extends React.Component {
return React.cloneElement(child, { ref });
}

if (this.props.children) {
return <div ref={this.refElement}>{this.props.children}</div>;
}

// We need an element that we can locate in the DOM to determine where it is
// rendered relative to the top of its context.
return <span ref={this.refElement} style={{ fontSize: 0 }} />;
}
}

Waypoint.propTypes = {
children: PropTypes.node,
noWrapper: PropTypes.bool,
children: PropTypes.element,
debug: PropTypes.bool,
onEnter: PropTypes.func,
onLeave: PropTypes.func,
Expand Down

0 comments on commit bb5bea6

Please sign in to comment.