feat(addon): #417 infinite scroll
This commit is contained in:
Родитель
6f8b72ce3f
Коммит
620d1097f0
|
@ -9,5 +9,13 @@ module.exports = {
|
|||
HIGHLIGHTS_THRESHOLDS: {
|
||||
created: "-3 day",
|
||||
visited: "-30 minutes",
|
||||
}
|
||||
},
|
||||
|
||||
// This is how many pixels before the bottom that
|
||||
// infinite scroll is triggered
|
||||
INFINITE_SCROLL_THRESHOLD: 20,
|
||||
|
||||
// How many pixels offset for the infinite scroll top
|
||||
// due to the header?
|
||||
SCROLL_TOP_OFFSET: 50
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ const constants = {
|
|||
"UNBLOCK_ALL",
|
||||
"SHARE",
|
||||
"LOAD_MORE",
|
||||
"LOAD_MORE_SCROLL",
|
||||
"SEARCH"
|
||||
]),
|
||||
sources: new Set([
|
||||
|
|
|
@ -11,19 +11,10 @@
|
|||
.spinner {
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
width: $loader-height;
|
||||
height: $loader-height;
|
||||
width: $loader-size;
|
||||
height: $loader-size;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
border-top: $loader-border-width solid $loader-color-light;
|
||||
border-right: $loader-border-width solid $loader-color-light;
|
||||
border-bottom: $loader-border-width solid $loader-color;
|
||||
border-left: $loader-border-width solid $loader-color;
|
||||
animation: spin $loader-duration linear infinite;
|
||||
background-image: url('img/loading@2x.png');
|
||||
background-size: $loader-size $loader-size;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
|
|
@ -1,43 +1,18 @@
|
|||
const React = require("react");
|
||||
const {connect} = require("react-redux");
|
||||
const {selectBookmarks} = require("selectors/selectors");
|
||||
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
|
||||
const {RequestMoreBookmarks, NotifyEvent} = require("common/action-manager").actions;
|
||||
const LoadMore = require("components/LoadMore/LoadMore");
|
||||
const classNames = require("classnames");
|
||||
|
||||
const PAGE_NAME = "TIMELINE_BOOKMARKS";
|
||||
const {RequestMoreBookmarks} = require("common/action-manager").actions;
|
||||
const TimelineFeed = require("./TimelineFeed");
|
||||
|
||||
const TimelineBookmarks = React.createClass({
|
||||
getMore() {
|
||||
const bookmarks = this.props.Bookmarks.rows;
|
||||
if (!bookmarks.length) {
|
||||
return;
|
||||
}
|
||||
const beforeDate = bookmarks[bookmarks.length - 1].lastModified;
|
||||
this.props.dispatch(RequestMoreBookmarks(beforeDate));
|
||||
this.props.dispatch(NotifyEvent({
|
||||
event: "LOAD_MORE",
|
||||
page: "TIMELINE_BOOKMARKS",
|
||||
source: "ACTIVITY_FEED"
|
||||
}));
|
||||
},
|
||||
render() {
|
||||
const props = this.props;
|
||||
return (<div className={classNames("wrapper", "show-on-init", {on: props.Bookmarks.init})}>
|
||||
<GroupedActivityFeed
|
||||
sites={props.Bookmarks.rows}
|
||||
length={20}
|
||||
dateKey="bookmarkDateCreated"
|
||||
page={PAGE_NAME}
|
||||
showDateHeadings={true}
|
||||
/>
|
||||
<LoadMore
|
||||
loading={props.Bookmarks.isLoading}
|
||||
hidden={!props.Bookmarks.canLoadMore || !props.Bookmarks.rows.length}
|
||||
onClick={this.getMore}
|
||||
label="See more activity"/>
|
||||
</div>);
|
||||
return (<TimelineFeed
|
||||
loadMoreAction={RequestMoreBookmarks}
|
||||
dateKey={"bookmarkDateCreated"}
|
||||
pageName={"TIMELINE_BOOKMARKS"}
|
||||
Feed={props.Bookmarks}
|
||||
/>);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
const React = require("react");
|
||||
const {connect} = require("react-redux");
|
||||
const {justDispatch} = require("selectors/selectors");
|
||||
const {NotifyEvent} = require("common/action-manager").actions;
|
||||
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
|
||||
const Spotlight = require("components/Spotlight/Spotlight");
|
||||
const Loader = require("components/Loader/Loader");
|
||||
const classNames = require("classnames");
|
||||
const {INFINITE_SCROLL_THRESHOLD, SCROLL_TOP_OFFSET} = require("common/constants");
|
||||
const debounce = require("lodash.debounce");
|
||||
|
||||
const TimelineFeed = React.createClass({
|
||||
loadMore() {
|
||||
const items = this.props.Feed.rows;
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
const beforeDate = items[items.length - 1][this.props.dateKey];
|
||||
this.props.dispatch(this.props.loadMoreAction(beforeDate));
|
||||
this.props.dispatch(NotifyEvent({
|
||||
event: "LOAD_MORE_SCROLL",
|
||||
page: this.props.pageName,
|
||||
source: "ACTIVITY_FEED"
|
||||
}));
|
||||
},
|
||||
windowHeight: null,
|
||||
handleScroll(values) {
|
||||
const {Feed} = this.props;
|
||||
const {scrollTop, scrollHeight} = values;
|
||||
|
||||
if (!Feed.canLoadMore || Feed.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.windowHeight) {
|
||||
this.windowHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
if (scrollHeight - (scrollTop + this.windowHeight - SCROLL_TOP_OFFSET) < INFINITE_SCROLL_THRESHOLD) {
|
||||
this.loadMore();
|
||||
}
|
||||
},
|
||||
onResize: debounce(function() {
|
||||
this.windowHeight = window.innerHeight;
|
||||
}, 100),
|
||||
onScroll: debounce(function() {
|
||||
this.handleScroll({
|
||||
scrollHeight: this.refs.scrollElement.scrollHeight,
|
||||
scrollTop: this.refs.scrollElement.scrollTop
|
||||
});
|
||||
}, 100),
|
||||
componentDidUpdate(prevProps) {
|
||||
// Firefox will emit a scroll event if we don't do this
|
||||
// There is a weird behaviour that makes the scroll bar stick in a lower
|
||||
// position sometimes if we set scrollTop to 0 instead of 1
|
||||
if (!prevProps.Feed.init && this.props.Feed.init) {
|
||||
this.refs.scrollElement.scrollTop = 1;
|
||||
}
|
||||
},
|
||||
componentDidMount() {
|
||||
window.addEventListener("resize", this.onResize);
|
||||
},
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
},
|
||||
render() {
|
||||
const props = this.props;
|
||||
return (<section className="content" ref="scrollElement" onScroll={!props.Feed.isLoading && props.Feed.canLoadMore && this.onScroll}>
|
||||
<div ref="wrapper" className={classNames("wrapper", "show-on-init", {on: props.Feed.init})}>
|
||||
{props.Spotlight ? <Spotlight page={this.props.pageName} sites={props.Spotlight.rows} /> : null }
|
||||
<GroupedActivityFeed
|
||||
sites={props.Feed.rows}
|
||||
page={props.pageName}
|
||||
dateKey={props.dateKey}
|
||||
showDateHeadings={true} />
|
||||
<Loader className="infinite-scroll" ref="loader" show={props.Feed.isLoading} />
|
||||
</div>
|
||||
</section>);
|
||||
}
|
||||
});
|
||||
|
||||
TimelineFeed.propTypes = {
|
||||
Spotlight: React.PropTypes.object,
|
||||
Feed: React.PropTypes.object.isRequired,
|
||||
pageName: React.PropTypes.string.isRequired,
|
||||
loadMoreAction: React.PropTypes.func.isRequired,
|
||||
dateKey: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
module.exports = connect(justDispatch)(TimelineFeed);
|
||||
module.exports.TimelineFeed = TimelineFeed;
|
|
@ -1,39 +1,19 @@
|
|||
const React = require("react");
|
||||
const {connect} = require("react-redux");
|
||||
const {selectHistory} = require("selectors/selectors");
|
||||
const {RequestMoreRecentLinks, NotifyEvent} = require("common/action-manager").actions;
|
||||
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
|
||||
const Spotlight = require("components/Spotlight/Spotlight");
|
||||
const LoadMore = require("components/LoadMore/LoadMore");
|
||||
const classNames = require("classnames");
|
||||
|
||||
const PAGE_NAME = "TIMELINE_ALL";
|
||||
const {RequestMoreRecentLinks} = require("common/action-manager").actions;
|
||||
const TimelineFeed = require("./TimelineFeed");
|
||||
|
||||
const TimelineHistory = React.createClass({
|
||||
getMore() {
|
||||
const history = this.props.History.rows;
|
||||
if (!history.length) {
|
||||
return;
|
||||
}
|
||||
const beforeDate = history[history.length - 1].lastVisitDate;
|
||||
this.props.dispatch(RequestMoreRecentLinks(beforeDate));
|
||||
this.props.dispatch(NotifyEvent({
|
||||
event: "LOAD_MORE",
|
||||
page: "TIMELINE_ALL",
|
||||
source: "ACTIVITY_FEED"
|
||||
}));
|
||||
},
|
||||
render() {
|
||||
const props = this.props;
|
||||
return (<div className={classNames("wrapper", "show-on-init", {on: props.History.init})}>
|
||||
<Spotlight page={PAGE_NAME} sites={props.Spotlight.rows} />
|
||||
<GroupedActivityFeed
|
||||
sites={props.History.rows}
|
||||
page={PAGE_NAME}
|
||||
showDateHeadings={true} />
|
||||
<LoadMore loading={props.History.isLoading} hidden={!props.History.canLoadMore || !props.History.rows.length} onClick={this.getMore}
|
||||
label="See more activity"/>
|
||||
</div>);
|
||||
return (<TimelineFeed
|
||||
loadMoreAction={RequestMoreRecentLinks}
|
||||
dateKey={"lastVisitDate"}
|
||||
pageName={"TIMELINE_ALL"}
|
||||
Feed={props.History}
|
||||
Spotlight={props.Spotlight}
|
||||
/>);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -41,7 +21,5 @@ TimelineHistory.propTypes = {
|
|||
Spotlight: React.PropTypes.object.isRequired,
|
||||
History: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
module.exports = connect(selectHistory)(TimelineHistory);
|
||||
|
||||
module.exports.TimelineHistory = TimelineHistory;
|
||||
|
|
|
@ -36,9 +36,7 @@ const TimelinePage = React.createClass({
|
|||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
<section className="content">
|
||||
{this.props.children}
|
||||
</section>
|
||||
{this.props.children}
|
||||
</main>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
main.timeline {
|
||||
margin-top: 50px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.wrapper {
|
||||
margin-left: 20px;
|
||||
|
@ -66,5 +67,10 @@ main.timeline {
|
|||
background: $bg-grey;
|
||||
margin-bottom: 0;
|
||||
margin-left: $header-nav-width;
|
||||
|
||||
.infinite-scroll {
|
||||
margin-top: $base-gutter / 2;
|
||||
margin-left: $feed-gutter-h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const am = require("common/action-manager");
|
||||
const setRowsOrError = require("reducers/SetRowsOrError");
|
||||
const {LINKS_QUERY_LIMIT} = require("common/constants");
|
||||
|
||||
function setSearchState(type) {
|
||||
return (prevState = {currentEngine: {}, error: false, init: false}, action) => {
|
||||
|
@ -31,8 +30,8 @@ function setSearchState(type) {
|
|||
|
||||
module.exports = {
|
||||
TopSites: setRowsOrError("TOP_FRECENT_SITES_REQUEST", "TOP_FRECENT_SITES_RESPONSE"),
|
||||
History: setRowsOrError("RECENT_LINKS_REQUEST", "RECENT_LINKS_RESPONSE", LINKS_QUERY_LIMIT),
|
||||
Bookmarks: setRowsOrError("RECENT_BOOKMARKS_REQUEST", "RECENT_BOOKMARKS_RESPONSE", LINKS_QUERY_LIMIT),
|
||||
History: setRowsOrError("RECENT_LINKS_REQUEST", "RECENT_LINKS_RESPONSE"),
|
||||
Bookmarks: setRowsOrError("RECENT_BOOKMARKS_REQUEST", "RECENT_BOOKMARKS_RESPONSE"),
|
||||
Highlights: setRowsOrError("HIGHLIGHTS_LINKS_REQUEST", "HIGHLIGHTS_LINKS_RESPONSE"),
|
||||
Search: setSearchState("SEARCH_STATE_RESPONSE")
|
||||
};
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 22 KiB |
|
@ -91,12 +91,7 @@ $placeholder-border: 1px solid $faintest-black;
|
|||
$item-shadow: 0 1px 0 0 $faintest-black;
|
||||
$item-shadow-hover: 0 1px 0 0 $faintest-black, 0 0 0 5px $faintest-black;
|
||||
|
||||
$loader-height: 25px;
|
||||
$loader-width: 25px;
|
||||
$loader-border-width: 5px;
|
||||
$loader-color: $search-blue;
|
||||
$loader-color-light: rgba($loader-color, 0.1);
|
||||
$loader-duration: 1.3s;
|
||||
$loader-size: 16px;
|
||||
|
||||
$context-menu-shadow: 0 5px 10px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
$context-menu-font-size: 14px;
|
||||
|
|
|
@ -4,13 +4,17 @@ const ReactDOM = require("react-dom");
|
|||
const TestUtils = require("react-addons-test-utils");
|
||||
|
||||
const TimelinePage = require("components/TimelinePage/TimelinePage");
|
||||
const ConnectedTimelineFeed = require("components/TimelinePage/TimelineFeed");
|
||||
const {TimelineFeed} = ConnectedTimelineFeed;
|
||||
const ConnectedTimelineHistory = require("components/TimelinePage/TimelineHistory");
|
||||
const {TimelineHistory} = ConnectedTimelineHistory;
|
||||
const ConnectedTimelineBookmarks = require("components/TimelinePage/TimelineBookmarks");
|
||||
const {TimelineBookmarks} = ConnectedTimelineBookmarks;
|
||||
const {GroupedActivityFeed} = require("components/ActivityFeed/ActivityFeed");
|
||||
const LoadMore = require("components/LoadMore/LoadMore");
|
||||
const Loader = require("components/Loader/Loader");
|
||||
const Spotlight = require("components/Spotlight/Spotlight");
|
||||
const {mockData, renderWithProvider} = require("test/test-utils");
|
||||
const {INFINITE_SCROLL_THRESHOLD, SCROLL_TOP_OFFSET} = require("common/constants");
|
||||
|
||||
describe("Timeline", () => {
|
||||
|
||||
|
@ -45,15 +49,152 @@ describe("Timeline", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("TimelineFeed", () => {
|
||||
let instance;
|
||||
let loader;
|
||||
let loaderEl;
|
||||
|
||||
const fakeProps = {
|
||||
Feed: {
|
||||
init: true,
|
||||
isLoading: false,
|
||||
canLoadMore: true,
|
||||
rows: mockData.History.rows,
|
||||
},
|
||||
dateKey: "lastVisitDate",
|
||||
pageName: "TIMELINE_ALL",
|
||||
loadMoreAction: () => {}
|
||||
};
|
||||
|
||||
function setup(customProps = {}, dispatch) {
|
||||
const props = Object.assign({}, fakeProps, customProps);
|
||||
const connected = renderWithProvider(<ConnectedTimelineFeed {...props} />, dispatch && {dispatch});
|
||||
instance = TestUtils.findRenderedComponentWithType(connected, TimelineFeed);
|
||||
loader = TestUtils.findRenderedComponentWithType(instance, Loader);
|
||||
loaderEl = ReactDOM.findDOMNode(loader);
|
||||
}
|
||||
|
||||
beforeEach(setup);
|
||||
|
||||
it("should create a TimelineFeed", () => {
|
||||
assert.ok(TestUtils.isCompositeComponentWithType(instance, TimelineFeed));
|
||||
});
|
||||
|
||||
describe("Elements", () => {
|
||||
it("should render GroupedActivityFeed with correct data", () => {
|
||||
const activityFeed = TestUtils.findRenderedComponentWithType(instance, GroupedActivityFeed);
|
||||
assert.equal(activityFeed.props.sites, fakeProps.Feed.rows);
|
||||
assert.equal(activityFeed.props.dateKey, fakeProps.dateKey);
|
||||
});
|
||||
it("should not render a Spotlight if Spotlight data is missing", () => {
|
||||
assert.lengthOf(TestUtils.scryRenderedComponentsWithType(instance, Spotlight), 0);
|
||||
});
|
||||
it("should render a Spotlight if Spotlight data is provided", () => {
|
||||
setup({Spotlight: mockData.Highlights});
|
||||
assert.ok(TestUtils.findRenderedComponentWithType(instance, Spotlight));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Loader", () => {
|
||||
it("should have a Loader element", () => {
|
||||
assert.ok(loader);
|
||||
});
|
||||
it("should show Loader if History.isLoading is true", () => {
|
||||
setup({
|
||||
Feed: Object.assign({}, fakeProps.Feed, {isLoading: true}),
|
||||
});
|
||||
assert.equal(loaderEl.hidden, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#loadMore", () => {
|
||||
it("should dispatch loadMoreAction", done => {
|
||||
setup({loadMoreAction: () => "foo"}, action => {
|
||||
assert.equal(action, "foo");
|
||||
done();
|
||||
});
|
||||
instance.loadMore();
|
||||
});
|
||||
it("should select the {dateKey} of the last item", done => {
|
||||
setup({
|
||||
dateKey: "foo",
|
||||
Feed: Object.assign({}, fakeProps.Feed, {
|
||||
rows: [
|
||||
{url: "asd.com", foo: 3},
|
||||
{url: "blah.com", foo: 2},
|
||||
{url: "324ads.com", foo: 1}
|
||||
]
|
||||
}),
|
||||
loadMoreAction: date => {
|
||||
assert.equal(date, 1);
|
||||
done();
|
||||
}
|
||||
});
|
||||
instance.loadMore();
|
||||
});
|
||||
it("should dispatch a NotifyEvent", done => {
|
||||
setup({}, action => {
|
||||
if (action && action.type === "NOTIFY_USER_EVENT") {
|
||||
assert.equal(action.type, "NOTIFY_USER_EVENT");
|
||||
assert.equal(action.data.event, "LOAD_MORE_SCROLL");
|
||||
assert.equal(action.data.page, fakeProps.pageName);
|
||||
done();
|
||||
}
|
||||
});
|
||||
instance.loadMore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("#handleScroll", function() {
|
||||
it("should set this.windowHeight if it is falsey", () => {
|
||||
instance.windowHeight = null;
|
||||
instance.handleScroll({scrollTop: 0, scrollHeight: 5000});
|
||||
assert.equal(instance.windowHeight, window.innerHeight);
|
||||
});
|
||||
it("should not call loadMore if the scrollTop is before the threshold", () => {
|
||||
setup({loadMoreAction: () => {
|
||||
throw new Error("Should not call loadMore");
|
||||
}});
|
||||
instance.windowHeight = 200;
|
||||
instance.handleScroll({scrollTop: 0, scrollHeight: 400});
|
||||
});
|
||||
it("should loadMore if the scrollTop is past the threshold", done => {
|
||||
setup({loadMoreAction: () => done()});
|
||||
instance.windowHeight = 200;
|
||||
const scrollTop = 200 + SCROLL_TOP_OFFSET - INFINITE_SCROLL_THRESHOLD + 1;
|
||||
instance.handleScroll({scrollTop, scrollHeight: 200});
|
||||
});
|
||||
it("should not call loadMore if canLoadMore is false", () => {
|
||||
setup({
|
||||
Feed: Object.assign({}, fakeProps.Feed, {canLoadMore: false}),
|
||||
loadMoreAction: () => {
|
||||
throw new Error("Should not call loadMore");
|
||||
}
|
||||
});
|
||||
instance.windowHeight = 200;
|
||||
instance.handleScroll({scrollTop: 1000, scrollHeight: 200});
|
||||
});
|
||||
it("should not call loadMore if isLoading is true", () => {
|
||||
setup({
|
||||
Feed: Object.assign({}, fakeProps.Feed, {isLoading: true}),
|
||||
loadMoreAction: () => {
|
||||
throw new Error("Should not call loadMore");
|
||||
}
|
||||
});
|
||||
instance.windowHeight = 200;
|
||||
instance.handleScroll({scrollTop: 1000, scrollHeight: 200});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("TimelineHistory", () => {
|
||||
let instance;
|
||||
let loadMore;
|
||||
const fakeProps = mockData;
|
||||
|
||||
function setup(customProps = {}, dispatch) {
|
||||
const props = Object.assign({}, fakeProps, customProps);
|
||||
instance = renderWithProvider(<TimelineHistory {...props} />, dispatch && {dispatch});
|
||||
loadMore = TestUtils.findRenderedComponentWithType(instance, LoadMore);
|
||||
}
|
||||
|
||||
beforeEach(setup);
|
||||
|
@ -62,64 +203,20 @@ describe("Timeline", () => {
|
|||
assert.ok(TestUtils.isCompositeComponentWithType(instance, TimelineHistory));
|
||||
});
|
||||
|
||||
it("should render GroupedActivityFeed with correct data", () => {
|
||||
const activityFeed = TestUtils.findRenderedComponentWithType(instance, GroupedActivityFeed);
|
||||
assert.equal(activityFeed.props.sites, fakeProps.History.rows);
|
||||
});
|
||||
|
||||
it("should render the connected container with the correct props", () => {
|
||||
const container = renderWithProvider(<ConnectedTimelineHistory />);
|
||||
const inner = TestUtils.findRenderedComponentWithType(container, TimelineHistory);
|
||||
Object.keys(TimelineHistory.propTypes).forEach(key => assert.property(inner.props, key));
|
||||
});
|
||||
|
||||
it("should have a LoadMore element", () => {
|
||||
assert.ok(loadMore);
|
||||
});
|
||||
|
||||
it("should show a loader if History.isLoading is true", () => {
|
||||
setup({
|
||||
History: {
|
||||
isLoading: true,
|
||||
canLoadMore: true,
|
||||
rows: [{url: "https://foo.com"}]
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore.refs.loader).hidden, false);
|
||||
});
|
||||
|
||||
it("should hide LoadMore if canLoadMore is false", () => {
|
||||
setup({
|
||||
History: {
|
||||
isLoading: false,
|
||||
canLoadMore: false,
|
||||
rows: [{url: "https://foo.com"}]
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore).hidden, true);
|
||||
});
|
||||
|
||||
it("should hide LoadMore if rows are empty", () => {
|
||||
setup({
|
||||
History: {
|
||||
isLoading: false,
|
||||
canLoadMore: true,
|
||||
rows: []
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore).hidden, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TimelineBookmarks", () => {
|
||||
let instance;
|
||||
let loadMore;
|
||||
const fakeProps = mockData;
|
||||
|
||||
function setup(customProps = {}, dispatch) {
|
||||
const props = Object.assign({}, fakeProps, customProps);
|
||||
instance = renderWithProvider(<TimelineBookmarks {...props} />, dispatch && {dispatch});
|
||||
loadMore = TestUtils.findRenderedComponentWithType(instance, LoadMore);
|
||||
}
|
||||
|
||||
beforeEach(setup);
|
||||
|
@ -128,53 +225,11 @@ describe("Timeline", () => {
|
|||
assert.ok(TestUtils.isCompositeComponentWithType(instance, TimelineBookmarks));
|
||||
});
|
||||
|
||||
it("should render GroupedActivityFeed with correct data", () => {
|
||||
const activityFeed = TestUtils.findRenderedComponentWithType(instance, GroupedActivityFeed);
|
||||
assert.equal(activityFeed.props.sites, fakeProps.Bookmarks.rows);
|
||||
});
|
||||
|
||||
it("should render the connected container with the correct props", () => {
|
||||
const container = renderWithProvider(<ConnectedTimelineBookmarks />);
|
||||
const inner = TestUtils.findRenderedComponentWithType(container, TimelineBookmarks);
|
||||
Object.keys(TimelineBookmarks.propTypes).forEach(key => assert.property(inner.props, key));
|
||||
});
|
||||
|
||||
it("should have a LoadMore element", () => {
|
||||
assert.ok(loadMore);
|
||||
});
|
||||
|
||||
it("should show a loader if Bookmarks.isLoading is true", () => {
|
||||
setup({
|
||||
Bookmarks: {
|
||||
isLoading: true,
|
||||
canLoadMore: true,
|
||||
rows: [{url: "https://foo.com"}]
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore.refs.loader).hidden, false);
|
||||
});
|
||||
|
||||
it("should hide LoadMore if canLoadMore is false", () => {
|
||||
setup({
|
||||
Bookmarks: {
|
||||
isLoading: false,
|
||||
canLoadMore: false,
|
||||
rows: [{url: "https://foo.com"}]
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore).hidden, true);
|
||||
});
|
||||
|
||||
it("should hide LoadMore if rows are empty", () => {
|
||||
setup({
|
||||
Bookmarks: {
|
||||
isLoading: false,
|
||||
canLoadMore: true,
|
||||
rows: []
|
||||
}
|
||||
});
|
||||
assert.equal(ReactDOM.findDOMNode(loadMore).hidden, true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const {Provider} = require("react-redux");
|
||||
const mockData = require("lib/fake-data");
|
||||
const {selectNewTabSites} = require("selectors/selectors");
|
||||
|
@ -10,7 +11,7 @@ const DEFAULT_STORE = {
|
|||
subscribe: () => {}
|
||||
};
|
||||
|
||||
function createMockProvider(custom = {}) {
|
||||
function createMockProvider(custom) {
|
||||
const store = Object.assign({}, DEFAULT_STORE, custom);
|
||||
store.subscribe = () => {};
|
||||
return React.createClass({
|
||||
|
@ -20,9 +21,10 @@ function createMockProvider(custom = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
function renderWithProvider(component, store) {
|
||||
const ProviderWrapper = createMockProvider(store);
|
||||
const container = TestUtils.renderIntoDocument(<ProviderWrapper>{component}</ProviderWrapper>);
|
||||
function renderWithProvider(component, store, node) {
|
||||
const ProviderWrapper = createMockProvider(store && store);
|
||||
const render = node ? instance => ReactDOM.render(instance, node) : TestUtils.renderIntoDocument;
|
||||
const container = render(<ProviderWrapper>{component}</ProviderWrapper>);
|
||||
return TestUtils.findRenderedComponentWithType(container, component.type);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"classnames": "2.2.3",
|
||||
"fancy-dedupe": "0.1.0",
|
||||
"history": "1.17.0",
|
||||
"lodash.debounce": "4.0.6",
|
||||
"moment": "2.11.2",
|
||||
"react": "0.14.8",
|
||||
"react-dom": "0.14.8",
|
||||
|
|
Загрузка…
Ссылка в новой задаче