aboutsummaryrefslogtreecommitdiff
path: root/src/web/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/web/js')
-rw-r--r--src/web/js/actions/MenuActions.js36
-rw-r--r--src/web/js/actions/MiddlePanelActions.js54
-rw-r--r--src/web/js/app.js17
-rw-r--r--src/web/js/components/MainApp.react.js18
-rw-r--r--src/web/js/components/Menu.react.js119
-rw-r--r--src/web/js/components/MiddlePanel.react.js103
-rw-r--r--src/web/js/constants/JarrConstants.js28
-rw-r--r--src/web/js/dispatcher/JarrDispatcher.js16
-rw-r--r--src/web/js/dispatcher/__tests__/AppDispatcher-test.js72
-rw-r--r--src/web/js/stores/MenuStore.js56
-rw-r--r--src/web/js/stores/MiddlePanelStore.js68
-rw-r--r--src/web/js/stores/__tests__/TodoStore-test.js90
12 files changed, 677 insertions, 0 deletions
diff --git a/src/web/js/actions/MenuActions.js b/src/web/js/actions/MenuActions.js
new file mode 100644
index 00000000..ce3a1030
--- /dev/null
+++ b/src/web/js/actions/MenuActions.js
@@ -0,0 +1,36 @@
+var JarrDispatcher = require('../dispatcher/JarrDispatcher');
+var MenuActionTypes = require('../constants/JarrConstants').MenuActionTypes;
+
+
+var MenuActions = {
+ // PARENT FILTERS
+ reload: function() {
+ $.getJSON('/menu', function(payload) {
+ JarrDispatcher.dispatch({
+ type: MenuActionTypes.RELOAD_MENU,
+ categories: payload.categories,
+ all_unread_count: payload.all_unread_count,
+ });
+ });
+ },
+ setFilterMenuAll: function() {
+ JarrDispatcher.dispatch({
+ component: 'menu',
+ type: MenuActionTypes.MENU_FILTER_ALL,
+ });
+ },
+ setFilterMenuUnread: function() {
+ JarrDispatcher.dispatch({
+ component: 'menu',
+ type: MenuActionTypes.MENU_FILTER_UNREAD,
+ });
+ },
+ setFilterMenuError: function() {
+ JarrDispatcher.dispatch({
+ type: MenuActionTypes.MENU_FILTER_ERROR,
+ });
+ },
+
+};
+
+module.exports = MenuActions;
diff --git a/src/web/js/actions/MiddlePanelActions.js b/src/web/js/actions/MiddlePanelActions.js
new file mode 100644
index 00000000..9877d0d5
--- /dev/null
+++ b/src/web/js/actions/MiddlePanelActions.js
@@ -0,0 +1,54 @@
+var JarrDispatcher = require('../dispatcher/JarrDispatcher');
+var MiddlePanelActionTypes = require('../constants/JarrConstants').MiddlePanelActionTypes;
+
+
+var MiddlePanelActions = {
+ reload: function() {
+ $.getJSON('/middle_panel', function(payload) {
+ JarrDispatcher.dispatch({
+ type: MiddlePanelActionTypes.RELOAD_MIDDLE_PANEL,
+ articles: payload.articles,
+ });
+ });
+ },
+ removeParentFilter: function(parent_type, parent_id) {
+ JarrDispatcher.dispatch({
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER,
+ parent_type: null,
+ parent_id: null,
+ });
+ },
+ setCategoryFilter: function(category_id) {
+ JarrDispatcher.dispatch({
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER,
+ parent_type: 'category',
+ parent_id: category_id,
+ });
+ },
+ setFeedFilter: function(feed_id) {
+ JarrDispatcher.dispatch({
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER,
+ parent_type: 'feed',
+ parent_id: feed_id,
+ });
+ },
+ setFilterMiddlePanelAll: function() {
+ JarrDispatcher.dispatch({
+ component: 'middle_panel',
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_ALL,
+ });
+ },
+ setFilterMiddlePanelUnread: function() {
+ JarrDispatcher.dispatch({
+ component: 'middle_panel',
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_UNREAD,
+ });
+ },
+ setFilterMiddlePanelUnread: function() {
+ JarrDispatcher.dispatch({
+ type: MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_LIKED,
+ });
+ },
+};
+
+module.exports = MiddlePanelActions;
diff --git a/src/web/js/app.js b/src/web/js/app.js
new file mode 100644
index 00000000..603172b3
--- /dev/null
+++ b/src/web/js/app.js
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2014, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var React = require('react');
+
+var MainApp = require('./components/MainApp.react');
+
+React.render(
+ <MainApp />,
+ document.getElementById('jarrapp')
+);
diff --git a/src/web/js/components/MainApp.react.js b/src/web/js/components/MainApp.react.js
new file mode 100644
index 00000000..743c9510
--- /dev/null
+++ b/src/web/js/components/MainApp.react.js
@@ -0,0 +1,18 @@
+var Menu = require('./Menu.react');
+var MiddlePanel = require('./MiddlePanel.react');
+var React = require('react');
+
+
+var MainApp = React.createClass({
+ render: function() {
+ return (<div className="container-fluid">
+ <div className="row row-offcanvas row-offcanvas-left">
+ <Menu />
+ <MiddlePanel />
+ </div>
+ </div>
+ );
+ },
+});
+
+module.exports = MainApp;
diff --git a/src/web/js/components/Menu.react.js b/src/web/js/components/Menu.react.js
new file mode 100644
index 00000000..caf8c3a8
--- /dev/null
+++ b/src/web/js/components/Menu.react.js
@@ -0,0 +1,119 @@
+var React = require('react');
+var MenuStore = require('../stores/MenuStore');
+var MenuActions = require('../actions/MenuActions');
+var MiddlePanelActions = require('../actions/MiddlePanelActions');
+
+var FeedItem = React.createClass({
+ propTypes: {feed_id: React.PropTypes.number.isRequired,
+ title: React.PropTypes.string.isRequired,
+ unread: React.PropTypes.number.isRequired,
+ icon_url: React.PropTypes.string,
+ },
+ getInitialState: function() {
+ return {feed_id: this.props.feed_id,
+ title: this.props.title,
+ unread: this.props.unread,
+ icon_url: this.props.icon_url,
+ };
+ },
+ render: function() {
+ var unread = undefined;
+ var icon = undefined;
+ if(this.state.icon_url){
+ icon = (<img width="16px" src={this.state.icon_url} />);
+ } else {
+ icon = (<span className="glyphicon glyphicon-ban-circle" />);
+ }
+ if(this.state.unread){
+ unread = (
+ <span className="badge pull-right">
+ {this.state.unread}
+ </span>
+ );
+ }
+ return (<li onMouseDown={this.handleClick}>
+ {icon} {this.state.title} {unread}
+ </li>
+ );
+ },
+ handleClick: function() {
+ MiddlePanelActions.setFeedFilter(this.state.feed_id);
+ },
+});
+
+var Category = React.createClass({
+ propTypes: {category_id: React.PropTypes.number.isRequired,
+ name: React.PropTypes.string.isRequired,
+ feeds: React.PropTypes.array.isRequired,
+ unread: React.PropTypes.number.isRequired,
+ },
+ getInitialState: function() {
+ return {category_id: this.props.category_id,
+ name: this.props.name,
+ feeds: this.props.feeds,
+ unread: this.props.unread,
+ };
+ },
+ render: function() {
+ unread = undefined;
+ if(this.state.unread){
+ unread = (
+ <span className="badge pull-right">
+ {this.state.unread}
+ </span>
+ );
+ }
+ return (<div>
+ <h3 onMouseDown={this.handleClick}>
+ {this.state.name} {unread}
+ </h3>
+ <ul className="nav nav-sidebar">
+ {this.state.feeds.map(function(feed){
+ return <FeedItem key={"feed" + feed.id}
+ feed_id={feed.id}
+ title={feed.title}
+ unread={feed.unread}
+ icon_url={feed.icon_url} />;})}
+ </ul>
+ </div>
+ );
+ },
+ handleClick: function() {
+ MiddlePanelActions.setCategoryFilter(this.state.category_id);
+ },
+});
+
+var Menu = React.createClass({
+ getInitialState: function() {
+ return {categories: [], all_unread_count: 0};
+ },
+ render: function() {
+ return (<div id="sidebar" data-spy="affix" role="navigation"
+ className="col-md-2 sidebar sidebar-offcanvas pre-scrollable hidden-sm hidden-xs affix">
+ {this.state.categories.map(function(category){
+ return (<Category key={"cat" + category.id}
+ category_id={category.id}
+ feeds={category.feeds}
+ name={category.name}
+ unread={category.unread} />);
+ })}
+
+ </div>
+ );
+ },
+
+ componentDidMount: function() {
+ MenuActions.reload();
+ MenuStore.addChangeListener(this._onChange);
+ },
+ componentWillUnmount: function() {
+ MenuStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ var datas = MenuStore.getAll();
+ this.setState({categories: datas.categories,
+ all_unread_count: datas.all_unread_count});
+ },
+});
+
+module.exports = Menu;
diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js
new file mode 100644
index 00000000..51d582c0
--- /dev/null
+++ b/src/web/js/components/MiddlePanel.react.js
@@ -0,0 +1,103 @@
+var React = require('react');
+var MiddlePanelStore = require('../stores/MiddlePanelStore');
+var MiddlePanelActions = require('../actions/MiddlePanelActions');
+
+var TableLine = React.createClass({
+ propTypes: {article_id: React.PropTypes.number.isRequired,
+ feed_title: React.PropTypes.string.isRequired,
+ icon_url: React.PropTypes.string,
+ title: React.PropTypes.string.isRequired,
+ date: React.PropTypes.string.isRequired,
+ read: React.PropTypes.bool.isRequired,
+ liked: React.PropTypes.bool.isRequired,
+ },
+ getInitialState: function() {
+ return {article_id: this.props.article_id,
+ title: this.props.title,
+ icon_url: this.props.icon_url,
+ feed_title: this.props.feed_title,
+ date: this.props.date,
+ read: this.props.read,
+ liked: this.props.liked,
+ };
+ },
+ render: function() {
+ var read = this.state.read ? 'r' : '';
+ var liked = this.state.liked ? 'l' : '';
+ var icon = undefined;
+ if(this.state.icon_url){
+ icon = (<img width="16px" src={this.state.icon_url} />);
+ } else {
+ icon = (<span className="glyphicon glyphicon-ban-circle" />);
+ }
+ return (
+ <tr>
+ <td>{icon}{liked}</td>
+ <td>
+ <a href={'/redirect/' + this.state.article_id}>
+ {this.state.feed_title}
+ </a>
+ </td>
+ <td>
+ <a href={'/article/' + this.state.article_id}>
+ {this.state.title}
+ </a>
+ </td>
+ <td>{this.state.date}</td>
+ </tr>
+ );
+ },
+});
+
+var TableBody = React.createClass({
+ propTypes: {articles: React.PropTypes.array.isRequired,
+ },
+ getInitialState: function() {
+ return {articles: this.props.articles,
+ };
+ },
+ render: function() {
+ return (<div className="table-responsive">
+ <table className="table table-striped strict-table">
+ <tbody>
+ {this.state.articles.map(function(article){
+ return (<TableLine key={"article" + article.article_id}
+ title={article.title}
+ icon_url={article.icon_url}
+ read={article.read}
+ liked={article.liked}
+ date={article.date}
+ article_id={article.article_id}
+ feed_title={article.feed_title} />);})}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+});
+
+var MiddlePanel = React.createClass({
+ getInitialState: function() {
+ return {articles: []};
+ },
+ render: function() {
+ var body = null;
+ if(this.state.articles.length) {
+ body = (<TableBody articles={this.state.articles} />);
+ }
+ return (<div className="col-md-offset-2 col-md-10 main">{body}</div>);
+ },
+ componentDidMount: function() {
+ MiddlePanelActions.reload();
+ MiddlePanelStore.addChangeListener(this._onChange);
+ },
+ componentWillUnmount: function() {
+ MiddlePanelStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ var datas = MiddlePanelStore.getAll();
+ this.setState({articles: datas.articles});
+ },
+});
+
+module.exports = MiddlePanel;
diff --git a/src/web/js/constants/JarrConstants.js b/src/web/js/constants/JarrConstants.js
new file mode 100644
index 00000000..a0850283
--- /dev/null
+++ b/src/web/js/constants/JarrConstants.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * TodoConstants
+ */
+
+var keyMirror = require('keymirror');
+
+module.exports = {
+ MenuActionTypes: keyMirror({
+ RELOAD_MENU: null,
+ MENU_FILTER_ALL: null,
+ MENU_FILTER_UNREAD: null,
+ MENU_FILTER_ERROR: null,
+ }),
+ MiddlePanelActionTypes: keyMirror({
+ RELOAD_MIDDLE_PANEL: null,
+ MIDDLE_PANEL_PARENT_FILTER: null,
+ MIDDLE_PANEL_FILTER_ALL: null,
+ MIDDLE_PANEL_FILTER_UNREAD: null,
+ MIDDLE_PANEL_FILTER_LIKED: null,
+ }),
+};
diff --git a/src/web/js/dispatcher/JarrDispatcher.js b/src/web/js/dispatcher/JarrDispatcher.js
new file mode 100644
index 00000000..56da186f
--- /dev/null
+++ b/src/web/js/dispatcher/JarrDispatcher.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * AppDispatcher
+ *
+ * A singleton that operates as the central hub for application updates.
+ */
+
+var Dispatcher = require('flux').Dispatcher;
+
+module.exports = new Dispatcher();
diff --git a/src/web/js/dispatcher/__tests__/AppDispatcher-test.js b/src/web/js/dispatcher/__tests__/AppDispatcher-test.js
new file mode 100644
index 00000000..d3a35fc5
--- /dev/null
+++ b/src/web/js/dispatcher/__tests__/AppDispatcher-test.js
@@ -0,0 +1,72 @@
+"use strict";
+
+jest.autoMockOff();
+
+describe('AppDispatcher', function() {
+ var AppDispatcher;
+
+ beforeEach(function() {
+ AppDispatcher = require('../AppDispatcher.js');
+ });
+
+ it('sends actions to subscribers', function() {
+ var listener = jest.genMockFunction();
+ AppDispatcher.register(listener);
+
+ var payload = {};
+ AppDispatcher.dispatch(payload);
+ expect(listener.mock.calls.length).toBe(1);
+ expect(listener.mock.calls[0][0]).toBe(payload);
+ });
+
+ it('waits with chained dependencies properly', function() {
+ var payload = {};
+
+ var listener1Done = false;
+ var listener1 = function(pl) {
+ AppDispatcher.waitFor([index2, index4]);
+ // Second, third, and fourth listeners should have now been called
+ expect(listener2Done).toBe(true);
+ expect(listener3Done).toBe(true);
+ expect(listener4Done).toBe(true);
+ listener1Done = true;
+ };
+ var index1 = AppDispatcher.register(listener1);
+
+ var listener2Done = false;
+ var listener2 = function(pl) {
+ AppDispatcher.waitFor([index3]);
+ expect(listener3Done).toBe(true);
+ listener2Done = true;
+ };
+ var index2 = AppDispatcher.register(listener2);
+
+ var listener3Done = false;
+ var listener3 = function(pl) {
+ listener3Done = true;
+ };
+ var index3 = AppDispatcher.register(listener3);
+
+ var listener4Done = false;
+ var listener4 = function(pl) {
+ AppDispatcher.waitFor([index3]);
+ expect(listener3Done).toBe(true);
+ listener4Done = true;
+ };
+ var index4 = AppDispatcher.register(listener4);
+
+ runs(function() {
+ AppDispatcher.dispatch(payload);
+ });
+
+ waitsFor(function() {
+ return listener1Done;
+ }, "Not all subscribers were properly called", 500);
+
+ runs(function() {
+ expect(listener1Done).toBe(true);
+ expect(listener2Done).toBe(true);
+ expect(listener3Done).toBe(true);
+ });
+ });
+});
diff --git a/src/web/js/stores/MenuStore.js b/src/web/js/stores/MenuStore.js
new file mode 100644
index 00000000..d7478091
--- /dev/null
+++ b/src/web/js/stores/MenuStore.js
@@ -0,0 +1,56 @@
+var JarrDispatcher = require('../dispatcher/JarrDispatcher');
+var MenuActionTypes = require('../constants/JarrConstants').MenuActionTypes;
+var EventEmitter = require('events').EventEmitter;
+var CHANGE_EVENT = 'change_menu';
+var assign = require('object-assign');
+
+
+var MenuStore = assign({}, EventEmitter.prototype, {
+ _datas: {filter: 'all', categories: [], all_unread_count: 0},
+ getAll: function() {
+ return this._datas;
+ },
+ setFilter: function(value) {
+ if(this._datas.filter != value) {
+ this._datas.filter = value;
+ this.emitChange();
+ }
+ },
+ readFeedArticle: function(feed_id) {
+ // TODO
+ },
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+});
+
+
+MenuStore.dispatchToken = JarrDispatcher.register(function(action) {
+ switch(action.type) {
+ case MenuActionTypes.RELOAD_MENU:
+ MenuStore._datas['categories'] = action.categories;
+ MenuStore._datas['all_unread_count'] = action.all_unread_count;
+ MenuStore.emitChange();
+ break;
+ case MenuActionTypes.MENU_FILTER_ALL:
+ MenuStore.setFilter('all');
+ break;
+ case MenuActionTypes.MENU_FILTER_UNREAD:
+ MenuStore.setFilter('unread');
+ break;
+ case MenuActionTypes.MENU_FILTER_ERROR:
+ MenuStore.setFilter('error');
+ break;
+
+ default:
+ // do nothing
+ }
+});
+
+module.exports = MenuStore;
diff --git a/src/web/js/stores/MiddlePanelStore.js b/src/web/js/stores/MiddlePanelStore.js
new file mode 100644
index 00000000..d5744e20
--- /dev/null
+++ b/src/web/js/stores/MiddlePanelStore.js
@@ -0,0 +1,68 @@
+var JarrDispatcher = require('../dispatcher/JarrDispatcher');
+var MiddlePanelActionTypes = require('../constants/JarrConstants').MiddlePanelActionTypes;
+var EventEmitter = require('events').EventEmitter;
+var CHANGE_EVENT = 'change_middle_panel';
+var assign = require('object-assign');
+
+
+var MiddlePanelStore = assign({}, EventEmitter.prototype, {
+ _datas: {filter: 'unread', articles: [],
+ parent_filter_type: null, parent_filter_id: null},
+ getAll: function() {
+ return this._datas;
+ },
+ setFilter: function(value) {
+ if(this._datas.filter != value) {
+ this._datas.filter = value;
+ this.emitChange();
+ }
+ },
+ setParentFilter: function(type, value) {
+ if(this._datas['parent_filter_id'] != value
+ || this._datas['parent_filter_type'] != type) {
+ this._datas['parent_filter_type'] = type;
+ this._datas['parent_filter_id'] = value;
+ this.emitChange();
+ }
+ },
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+});
+
+
+MiddlePanelStore.dispatchToken = JarrDispatcher.register(function(action) {
+ switch(action.type) {
+ case MiddlePanelActionTypes.RELOAD_MIDDLE_PANEL:
+ MiddlePanelStore._datas['articles'] = action.articles;
+ MiddlePanelStore.emitChange();
+ break;
+ // PARENT FILTER
+ case MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER:
+ MiddlePanelStore.setParentFilter(action.parent_type,
+ action.filter_id);
+ break;
+ // FILTER
+ case MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_ALL:
+ MiddlePanelStore.setFilter('all');
+ break;
+ case MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_UNREAD:
+ MiddlePanelStore.setFilter('unread');
+ break;
+ case MiddlePanelActionTypes.MIDDLE_PANEL_FILTER_LIKED:
+ MiddlePanelStore.setFilter('liked');
+ break;
+
+
+ default:
+ // do nothing
+ }
+});
+
+module.exports = MiddlePanelStore;
diff --git a/src/web/js/stores/__tests__/TodoStore-test.js b/src/web/js/stores/__tests__/TodoStore-test.js
new file mode 100644
index 00000000..6da6cd3c
--- /dev/null
+++ b/src/web/js/stores/__tests__/TodoStore-test.js
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * TodoStore-test
+ */
+
+jest.dontMock('../../constants/TodoConstants');
+jest.dontMock('../TodoStore');
+jest.dontMock('object-assign');
+
+describe('TodoStore', function() {
+
+ var TodoConstants = require('../../constants/TodoConstants');
+ var AppDispatcher;
+ var TodoStore;
+ var callback;
+
+ // mock actions
+ var actionTodoCreate = {
+ actionType: TodoConstants.TODO_CREATE,
+ text: 'foo'
+ };
+ var actionTodoDestroy = {
+ actionType: TodoConstants.TODO_DESTROY,
+ id: 'replace me in test'
+ };
+
+ beforeEach(function() {
+ AppDispatcher = require('../../dispatcher/AppDispatcher');
+ TodoStore = require('../TodoStore');
+ callback = AppDispatcher.register.mock.calls[0][0];
+ });
+
+ it('registers a callback with the dispatcher', function() {
+ expect(AppDispatcher.register.mock.calls.length).toBe(1);
+ });
+
+ it('should initialize with no to-do items', function() {
+ var all = TodoStore.getAll();
+ expect(all).toEqual({});
+ });
+
+ it('creates a to-do item', function() {
+ callback(actionTodoCreate);
+ var all = TodoStore.getAll();
+ var keys = Object.keys(all);
+ expect(keys.length).toBe(1);
+ expect(all[keys[0]].text).toEqual('foo');
+ });
+
+ it('destroys a to-do item', function() {
+ callback(actionTodoCreate);
+ var all = TodoStore.getAll();
+ var keys = Object.keys(all);
+ expect(keys.length).toBe(1);
+ actionTodoDestroy.id = keys[0];
+ callback(actionTodoDestroy);
+ expect(all[keys[0]]).toBeUndefined();
+ });
+
+ it('can determine whether all to-do items are complete', function() {
+ var i = 0;
+ for (; i < 3; i++) {
+ callback(actionTodoCreate);
+ }
+ expect(Object.keys(TodoStore.getAll()).length).toBe(3);
+ expect(TodoStore.areAllComplete()).toBe(false);
+
+ var all = TodoStore.getAll();
+ for (key in all) {
+ callback({
+ actionType: TodoConstants.TODO_COMPLETE,
+ id: key
+ });
+ }
+ expect(TodoStore.areAllComplete()).toBe(true);
+
+ callback({
+ actionType: TodoConstants.TODO_UNDO_COMPLETE,
+ id: key
+ });
+ expect(TodoStore.areAllComplete()).toBe(false);
+ });
+
+});
bgstack15