From 0fa0f63ffe218425b39b47220ccf2e72286339ac Mon Sep 17 00:00:00 2001 From: geobas Date: Wed, 19 Jul 2017 23:52:31 +0300 Subject: [PATCH 01/24] Replace gulp with webpack --- .gitignore | 7 +- {builds/app/js => build/assets}/data.json | 0 .../assets}/images/wisdompetlogo.svg | 0 {builds/app/css => build/assets}/style.css | 2 +- {builds/app => build}/index.html | 4 +- builds/app/js/app.js | 36331 ---------------- gulpfile.js | 42 - package.json | 34 +- {process => src}/js/AddAppointment.js | 0 {process => src}/js/AptList.js | 0 {process => src}/js/SearchAppointments.js | 0 process/js/app.js => src/js/index.js | 4 +- webpack.config.js | 34 + 13 files changed, 63 insertions(+), 36395 deletions(-) rename {builds/app/js => build/assets}/data.json (100%) rename {builds/app => build/assets}/images/wisdompetlogo.svg (100%) rename {builds/app/css => build/assets}/style.css (97%) rename {builds/app => build}/index.html (95%) delete mode 100644 builds/app/js/app.js delete mode 100755 gulpfile.js rename {process => src}/js/AddAppointment.js (100%) rename {process => src}/js/AptList.js (100%) rename {process => src}/js/SearchAppointments.js (100%) rename process/js/app.js => src/js/index.js (96%) create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index 94a5cc5..a72f47a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.DS_Store -node_modules -.tmp -./builds/app/js/app.js +node_modules/ +./build/assets/app.js +npm-debug.log \ No newline at end of file diff --git a/builds/app/js/data.json b/build/assets/data.json similarity index 100% rename from builds/app/js/data.json rename to build/assets/data.json diff --git a/builds/app/images/wisdompetlogo.svg b/build/assets/images/wisdompetlogo.svg similarity index 100% rename from builds/app/images/wisdompetlogo.svg rename to build/assets/images/wisdompetlogo.svg diff --git a/builds/app/css/style.css b/build/assets/style.css similarity index 97% rename from builds/app/css/style.css rename to build/assets/style.css index d0e3def..b42cf2a 100644 --- a/builds/app/css/style.css +++ b/build/assets/style.css @@ -32,7 +32,7 @@ header .navbar-default { /** Navbar Brand **/ header .navbar-brand { - background: url(../images/wisdompetlogo.svg); + background: url(images/wisdompetlogo.svg); background-repeat: no-repeat; background-position: 15px 0; } diff --git a/builds/app/index.html b/build/index.html similarity index 95% rename from builds/app/index.html rename to build/index.html index 969d837..4bb7490 100644 --- a/builds/app/index.html +++ b/build/index.html @@ -5,7 +5,7 @@ Building an Interface with React - +
@@ -49,6 +49,6 @@ - + diff --git a/builds/app/js/app.js b/builds/app/js/app.js deleted file mode 100644 index 072c500..0000000 --- a/builds/app/js/app.js +++ /dev/null @@ -1,36331 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o camelize('background-color') - * < "backgroundColor" - * - * @param {string} string - * @return {string} - */ -function camelize(string) { - return string.replace(_hyphenPattern, function (_, character) { - return character.toUpperCase(); - }); -} - -module.exports = camelize; -},{}],4:[function(require,module,exports){ -/** - * Copyright (c) 2013-present, 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. - * - * @typechecks - */ - -'use strict'; - -var camelize = require('./camelize'); - -var msPattern = /^-ms-/; - -/** - * Camelcases a hyphenated CSS property name, for example: - * - * > camelizeStyleName('background-color') - * < "backgroundColor" - * > camelizeStyleName('-moz-transition') - * < "MozTransition" - * > camelizeStyleName('-ms-transition') - * < "msTransition" - * - * As Andi Smith suggests - * (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix - * is converted to lowercase `ms`. - * - * @param {string} string - * @return {string} - */ -function camelizeStyleName(string) { - return camelize(string.replace(msPattern, 'ms-')); -} - -module.exports = camelizeStyleName; -},{"./camelize":3}],5:[function(require,module,exports){ -'use strict'; - -/** - * Copyright (c) 2013-present, 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 isTextNode = require('./isTextNode'); - -/*eslint-disable no-bitwise */ - -/** - * Checks if a given DOM node contains or is another DOM node. - */ -function containsNode(outerNode, innerNode) { - if (!outerNode || !innerNode) { - return false; - } else if (outerNode === innerNode) { - return true; - } else if (isTextNode(outerNode)) { - return false; - } else if (isTextNode(innerNode)) { - return containsNode(outerNode, innerNode.parentNode); - } else if ('contains' in outerNode) { - return outerNode.contains(innerNode); - } else if (outerNode.compareDocumentPosition) { - return !!(outerNode.compareDocumentPosition(innerNode) & 16); - } else { - return false; - } -} - -module.exports = containsNode; -},{"./isTextNode":18}],6:[function(require,module,exports){ -(function (process){ -'use strict'; - -/** - * Copyright (c) 2013-present, 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. - * - * @typechecks - */ - -var invariant = require('./invariant'); - -/** - * Convert array-like objects to arrays. - * - * This API assumes the caller knows the contents of the data type. For less - * well defined inputs use createArrayFromMixed. - * - * @param {object|function|filelist} obj - * @return {array} - */ -function toArray(obj) { - var length = obj.length; - - // Some browsers builtin objects can report typeof 'function' (e.g. NodeList - // in old versions of Safari). - !(!Array.isArray(obj) && (typeof obj === 'object' || typeof obj === 'function')) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Array-like object expected') : invariant(false) : void 0; - - !(typeof length === 'number') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object needs a length property') : invariant(false) : void 0; - - !(length === 0 || length - 1 in obj) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object should have keys for indices') : invariant(false) : void 0; - - !(typeof obj.callee !== 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object can\'t be `arguments`. Use rest params ' + '(function(...args) {}) or Array.from() instead.') : invariant(false) : void 0; - - // Old IE doesn't give collections access to hasOwnProperty. Assume inputs - // without method will throw during the slice call and skip straight to the - // fallback. - if (obj.hasOwnProperty) { - try { - return Array.prototype.slice.call(obj); - } catch (e) { - // IE < 9 does not support Array#slice on collections objects - } - } - - // Fall back to copying key by key. This assumes all keys have a value, - // so will not preserve sparsely populated inputs. - var ret = Array(length); - for (var ii = 0; ii < length; ii++) { - ret[ii] = obj[ii]; - } - return ret; -} - -/** - * Perform a heuristic test to determine if an object is "array-like". - * - * A monk asked Joshu, a Zen master, "Has a dog Buddha nature?" - * Joshu replied: "Mu." - * - * This function determines if its argument has "array nature": it returns - * true if the argument is an actual array, an `arguments' object, or an - * HTMLCollection (e.g. node.childNodes or node.getElementsByTagName()). - * - * It will return false for other array-like objects like Filelist. - * - * @param {*} obj - * @return {boolean} - */ -function hasArrayNature(obj) { - return( - // not null/false - !!obj && ( - // arrays are objects, NodeLists are functions in Safari - typeof obj == 'object' || typeof obj == 'function') && - // quacks like an array - 'length' in obj && - // not window - !('setInterval' in obj) && - // no DOM node should be considered an array-like - // a 'select' element has 'length' and 'item' properties on IE8 - typeof obj.nodeType != 'number' && ( - // a real array - Array.isArray(obj) || - // arguments - 'callee' in obj || - // HTMLCollection/NodeList - 'item' in obj) - ); -} - -/** - * Ensure that the argument is an array by wrapping it in an array if it is not. - * Creates a copy of the argument if it is already an array. - * - * This is mostly useful idiomatically: - * - * var createArrayFromMixed = require('createArrayFromMixed'); - * - * function takesOneOrMoreThings(things) { - * things = createArrayFromMixed(things); - * ... - * } - * - * This allows you to treat `things' as an array, but accept scalars in the API. - * - * If you need to convert an array-like object, like `arguments`, into an array - * use toArray instead. - * - * @param {*} obj - * @return {array} - */ -function createArrayFromMixed(obj) { - if (!hasArrayNature(obj)) { - return [obj]; - } else if (Array.isArray(obj)) { - return obj.slice(); - } else { - return toArray(obj); - } -} - -module.exports = createArrayFromMixed; -}).call(this,require("rH1JPG")) -},{"./invariant":16,"rH1JPG":28}],7:[function(require,module,exports){ -(function (process){ -'use strict'; - -/** - * Copyright (c) 2013-present, 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. - * - * @typechecks - */ - -/*eslint-disable fb-www/unsafe-html*/ - -var ExecutionEnvironment = require('./ExecutionEnvironment'); - -var createArrayFromMixed = require('./createArrayFromMixed'); -var getMarkupWrap = require('./getMarkupWrap'); -var invariant = require('./invariant'); - -/** - * Dummy container used to render all markup. - */ -var dummyNode = ExecutionEnvironment.canUseDOM ? document.createElement('div') : null; - -/** - * Pattern used by `getNodeName`. - */ -var nodeNamePattern = /^\s*<(\w+)/; - -/** - * Extracts the `nodeName` of the first element in a string of markup. - * - * @param {string} markup String of markup. - * @return {?string} Node name of the supplied markup. - */ -function getNodeName(markup) { - var nodeNameMatch = markup.match(nodeNamePattern); - return nodeNameMatch && nodeNameMatch[1].toLowerCase(); -} - -/** - * Creates an array containing the nodes rendered from the supplied markup. The - * optionally supplied `handleScript` function will be invoked once for each - * - - + + + + - + \ No newline at end of file diff --git a/package.json b/package.json index 857ca44..d6d9c4f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Project for Building an Interface with React Course on Lynda.com() => ES6 Port", "scripts": { - "start": "./node_modules/.bin/webpack-dev-server", + "start": "./node_modules/.bin/webpack-dev-server --progress --watch-poll", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -20,11 +20,12 @@ "babel-preset-stage-0": "^6.5.0", "lodash": "^4.15.0", "open-browser-webpack-plugin": "0.0.5", + "json-loader": "^0.5.4", "webpack": "^1.13.2", "webpack-dev-server": "^1.14.1" }, "dependencies": { - "react": "^15.3.0", - "react-dom": "^15.3.0" + "react": "^15.6.1", + "react-dom": "^15.6.1" } } diff --git a/src/js/AddAppointment.js b/src/js/AddAppointment.js index b21d3a9..cbc6509 100644 --- a/src/js/AddAppointment.js +++ b/src/js/AddAppointment.js @@ -1,80 +1,84 @@ -var React = require('react'); +import React,{Component} from 'react'; -var AddAppointment = React.createClass({ +export default class AddAppointment extends Component { - toggleAptDisplay: function() { - this.props.handleToggle(); - }, + constructor(props) { + super(props); + this.toggleAptDisplay = this.toggleAptDisplay.bind(this); + this.handleAdd = this.handleAdd.bind(this); + } - handleAdd: function(e) { - var tempItem = { - petName: this.refs.inputPetName.value, - ownerName: this.refs.inputOwnerName.value, - aptDate: this.refs.inputAptDate.value + ' ' + - this.refs.inputAptTime.value, - aptNotes: this.refs.inputAptNotes.value - } //tempItem - e.preventDefault(); - this.props.addApt(tempItem); - }, //handleAdd + componentWillMount() { + let display = this.props.bodyVisible ? 'block' : 'none'; + this.displayAptBody = { display }; + } - render: function() { + componentWillReceiveProps(nextProps) { + let display = nextProps.bodyVisible ? 'block' : 'none'; + this.displayAptBody = { display }; + } - var displayAptBody = { - display: this.props.bodyVisible ? 'block' : 'none' - }; + toggleAptDisplay() { + this.props.handleToggle(); + } - return( -
-
- Add Appointment
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
- -
- -
+ handleAdd(e) { + let tempItem = { + petName: this.refs.inputPetName.value, + ownerName: this.refs.inputOwnerName.value, + aptDate: this.refs.inputAptDate.value + ' ' + this.refs.inputAptTime.value, + aptNotes: this.refs.inputAptNotes.value + }; + e.preventDefault(); + this.props.addApt(tempItem); + } -
-
- -
- -
-
-
-
- -
-
-
-
-
- )//return - } //render -}); // AddAppointment + render() { + return ( +
+
+ Add Appointment +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
-module.exports = AddAppointment; +
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ ) + } + +} \ No newline at end of file diff --git a/src/js/AptList.js b/src/js/AptList.js index f935cba..7186d2b 100644 --- a/src/js/AptList.js +++ b/src/js/AptList.js @@ -1,30 +1,35 @@ -var React = require('react'); +import React,{Component} from 'react'; -var AptList = React.createClass({ +export default class MainInterface extends Component { - handleDelete: function() { - this.props.onDelete(this.props.whichItem) - }, + constructor(props) { + super(props); + this.handleDelete = this.handleDelete.bind(this); + } - render: function() { - return( -
  • -
    - -
    -
    -
    - {this.props.singleItem.petName} - {this.props.singleItem.aptDate} -
    -
    Owner: - {this.props.singleItem.ownerName}
    -
    {this.props.singleItem.aptNotes}
    -
    -
  • - ) // return - } // render -}); //AptList + handleDelete() { + this.props.onDelete(this.props.whichItem); + } -module.exports = AptList; + render() { + return ( +
  • +
    + +
    +
    +
    + { this.props.singleItem.petName } + { this.props.singleItem.aptDate } +
    +
    Owner: { this.props.singleItem.ownerName } +
    +
    { this.props.singleItem.aptNotes }
    +
    +
  • + ) + } + +} \ No newline at end of file diff --git a/src/js/MainInterface.js b/src/js/MainInterface.js new file mode 100644 index 0000000..6e89e5b --- /dev/null +++ b/src/js/MainInterface.js @@ -0,0 +1,111 @@ +import React,{Component} from 'react'; +import ReactDOM from 'react-dom'; +import _ from 'lodash'; + +import AptList from './AptList'; +import AddAppointment from './AddAppointment'; +import SearchAppointments from './SearchAppointments'; + +export default class MainInterface extends Component { + constructor(props) { + super(props); + this.state = { + aptBodyVisible: false, + orderBy: 'petName', + orderDir: 'asc', + queryText: '', + myAppointments: [] + }; + this.deleteMessage = this.deleteMessage.bind(this); + this.toggleAddDisplay = this.toggleAddDisplay.bind(this); + this.addItem = this.addItem.bind(this); + this.reOrder = this.reOrder.bind(this); + this.setQueryText = this.setQueryText.bind(this); + this.showAppointments = this.showAppointments.bind(this); + } + + componentDidMount() { + this.serverRequest = $.get('./assets/data.json', result => this.setState( { myAppointments: result } )); + } + + componentWillUnmount() { + this.serverRequest.abort(); + } + + deleteMessage(item) { + let newApts = _.without(this.state.myAppointments, item); + this.setState({ myAppointments: newApts }); + } + + toggleAddDisplay() { + let tempVisibility = !this.state.aptBodyVisible; + this.setState({ aptBodyVisible: tempVisibility }); + } + + addItem(tempItem) { + let tempApts = this.state.myAppointments; + tempApts.push(tempItem); + this.setState({ myAppointments: tempApts }); + } + + reOrder(orderBy, orderDir) { + this.setState({ orderBy, orderDir }); + } + + setQueryText(q) { + this.setState({ queryText: q }); + } + + showAppointments() { + let myAppointments = this.state.myAppointments; + let queryText = this.state.queryText; + let filteredApts = []; + + myAppointments.forEach( item => { + if ( + (item.petName.toLowerCase().indexOf(queryText)!=-1) || + (item.ownerName.toLowerCase().indexOf(queryText)!=-1) || + (item.aptDate.toLowerCase().indexOf(queryText)!=-1) || + (item.aptNotes.toLowerCase().indexOf(queryText)!=-1) + ) { filteredApts.push(item); } + } ); + + filteredApts = _.orderBy(filteredApts, item => item[this.state.orderBy].toLowerCase(), this.state.orderDir); + + filteredApts = filteredApts.map( (item, index) => { + return ( + + ) + } ); + + return filteredApts; + } + + render() { + + let filteredApts = this.showAppointments(); + + return ( +
    + + +
      { filteredApts }
    +
    + ) + } + +} \ No newline at end of file diff --git a/src/js/SearchAppointments.js b/src/js/SearchAppointments.js index bc2bbcf..80c808b 100644 --- a/src/js/SearchAppointments.js +++ b/src/js/SearchAppointments.js @@ -1,42 +1,54 @@ -var React = require('react'); +import React,{Component} from 'react'; -var SearchAppointments = React.createClass({ +export default class SearchAppointments extends Component { - handleSort: function(e) { - this.props.onReOrder(e.target.id, this.props.orderDir); - }, //handleSort + constructor(props) { + super(props); + this.handleSort = this.handleSort.bind(this); + this.handleOrder = this.handleOrder.bind(this); + this.handleSearch = this.handleSearch.bind(this); + } - handleOrder: function(e) { - this.props.onReOrder(this.props.orderBy, e.target.id); - }, //handleSort + handleSort(e) { + this.props.onReOrder(e.target.id, this.props.orderDir); + e.preventDefault(); + } - handleSearch: function(e) { - this.props.onSearch(e.target.value); - }, //handleSearch + handleOrder(e) { + this.props.onReOrder(this.props.orderBy, e.target.id); + e.preventDefault(); + } - render: function() { - return( -
    - -
    - ) // return - } // render -}); //SearchAppointments + handleSearch(e) { + this.props.onSearch(e.target.value); + } -module.exports = SearchAppointments; + render() { + return ( +
    + +
    + ) + } +} \ No newline at end of file diff --git a/src/js/index.js b/src/js/index.js index 132093d..e16f4dd 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,121 +1,7 @@ -var React = require('react'); -var ReactDOM = require('react-dom'); -var _ = require('lodash'); - -var AptList = require('./AptList'); -var AddAppointment = require('./AddAppointment'); -var SearchAppointments = require('./SearchAppointments'); - -var MainInterface = React.createClass({ - getInitialState: function() { - return { - aptBodyVisible: false, - orderBy: 'petName', - orderDir: 'asc', - queryText: '', - myAppointments: [] - } //return - }, //getInitialState - - componentDidMount: function() { - this.serverRequest = $.get('./assets/data.json', function(result) { - var tempApts = result; - this.setState({ - myAppointments: tempApts - }); //setState - }.bind(this)); - }, //componentDidMount - - componentWillUnmount: function() { - this.serverRequest.abort(); - }, //componentWillUnmount - - deleteMessage: function(item) { - var allApts = this.state.myAppointments; - var newApts = _.without(allApts, item); - this.setState({ - myAppointments: newApts - }); //setState - }, //deleteMessage - - toggleAddDisplay: function() { - var tempVisibility = !this.state.aptBodyVisible; - this.setState({ - aptBodyVisible: tempVisibility - }); //setState - }, //toggleAddDisplay - - addItem: function(tempItem) { - var tempApts = this.state.myAppointments; - tempApts.push(tempItem); - this.setState({ - myAppointments: tempApts - }); //setState - }, //addItem - - reOrder: function(orderBy, orderDir) { - this.setState({ - orderBy: orderBy, - orderDir: orderDir - }); //setState - }, //reOrder - - searchApts(q) { - this.setState({ - queryText: q - }); //setState - }, //searchApts - - render: function() { - var filteredApts = []; - var orderBy = this.state.orderBy; - var orderDir = this.state.orderDir; - var queryText = this.state.queryText; - var myAppointments = this.state.myAppointments; - - myAppointments.forEach(function(item) { - if( - (item.petName.toLowerCase().indexOf(queryText)!=-1) || - (item.ownerName.toLowerCase().indexOf(queryText)!=-1) || - (item.aptDate.toLowerCase().indexOf(queryText)!=-1) || - (item.aptNotes.toLowerCase().indexOf(queryText)!=-1) - ) { - filteredApts.push(item); - } - }); //forEach - - filteredApts = _.orderBy(filteredApts, function(item) { - return item[orderBy].toLowerCase(); - }, orderDir);//orderBy - - filteredApts = filteredApts.map(function(item, index) { - return( - - ) //return - }.bind(this)); //filteredApts.map - return ( -
    - - -
      {filteredApts}
    -
    - ) //return - } //render -}); //MainInterface +import React from 'react'; +import ReactDOM from 'react-dom'; +import MainInterface from './MainInterface'; ReactDOM.render( - , - document.getElementById('petAppointments') -); //render + , document.getElementById('petAppointments') +); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 205cd78..2c22232 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const path = require('path'); const OpenBrowserPlugin = require('open-browser-webpack-plugin'); module.exports = { + entry: path.resolve(__dirname, './src/js'), output: { @@ -22,13 +23,21 @@ module.exports = { module: { loaders: [ - { - test: /\.js$/, - exclude: /node_modules/, - loader: 'babel', - query: { - presets: ['react'] - } - }] + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel', + include: __dirname, + query: { + presets: ['es2015', 'react', 'stage-0'] + } + }, + // { + // test: /\.json$/, + // exclude: /(node_modules)/, + // loader: 'json-loader' + // }, + ] } + } \ No newline at end of file From db540cdd78ef61cef9686a024ad3648f1f764acd Mon Sep 17 00:00:00 2001 From: geobas Date: Fri, 21 Jul 2017 22:36:15 +0300 Subject: [PATCH 03/24] Update README --- README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 947b7df..27ad98d 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Building an Interface with React -This is the repository for my course, [Building an Interface with React](). The full course will be available at [lynda.com](http://lynda.com). -- [My Personal Website](http://raybo.org) - +This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). +The code is converted from ES5 to ES6 and gulp is replaced with webpack. ## Instructions This repository has branches for each of the videos in the course. You can use the branch pop up menu in github to switch to a specific branch and take a look at the course at that stage. Or you can simply add `/tree/BRANCH_NAME` to the URL to go to the branch you want to peek at. @@ -9,14 +8,9 @@ This repository has branches for each of the videos in the course. You can use t 1. Make sure you have these installed - [node.js](http://nodejs.org/) - [git](http://git-scm.com/) - - [gulp](http://gulpjs.com/) -2. Clone this repository into your local machine using the terminal (mac) or Gitbash (PC) `> git clone https://github.com/planetoftheweb/reactinterface.git` + - [webpack](http://webpack.js.org/) +2. Clone this repository into your local machine using the terminal (mac) or Gitbash (PC) `> git clone https://github.com/geobas/reactinterface` 3. CD to the folder `cd reactinterface` 4. Run `> npm install` to install the project dependencies -5. Run `> gulp` command to start the automation -6. Build something awesome - -For more help setting up a comprehensive Gulp.js workflow, check out [Web Project Workflows with Gulp.js, Git, and Browserify](http://www.lynda.com/Web-Web-Design-tutorials/Web-Project-Workflows-Gulpjs-Git-Browserify/154416-2.html). - -## More Stuff -Check out some of my [other courses on lynda.com](http://lynda.com/rayvillalobos). You can also check out my [youtube channel](http://youtube.com/planetoftheweb), [follow me on twitter](http://twitter.com/planetoftheweb), or read [my blog](http://raybo.org). +5. Run `> npm start` command to start webpack-dev-server +6. Build something awesome \ No newline at end of file From 5255dc33aab251fb82ef39868d6d5c1b5482b626 Mon Sep 17 00:00:00 2001 From: George Basteas Date: Fri, 21 Jul 2017 22:40:43 +0300 Subject: [PATCH 04/24] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27ad98d..0e8bb8e 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Building an Interface with React -This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). +This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). The code is converted from ES5 to ES6 and gulp is replaced with webpack. ## Instructions @@ -13,4 +13,4 @@ This repository has branches for each of the videos in the course. You can use t 3. CD to the folder `cd reactinterface` 4. Run `> npm install` to install the project dependencies 5. Run `> npm start` command to start webpack-dev-server -6. Build something awesome \ No newline at end of file +6. Build something awesome From 7ab9dfb06053b39fba44b47c99d282ed16cdd291 Mon Sep 17 00:00:00 2001 From: geobas Date: Fri, 21 Jul 2017 22:53:29 +0300 Subject: [PATCH 05/24] Upgrade to webpack 2.x --- package.json | 10 +++++----- webpack.config.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d6d9c4f..b03cbb7 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,17 @@ }, "author": "Geo Bas", "devDependencies": { - "babel-cli": "^6.11.4", - "babel-core": "^6.13.2", - "babel-loader": "^6.2.4", + "babel-cli": "^6.24.1", + "babel-core": "^6.25.0", + "babel-loader": "^7.1.1", "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", "babel-preset-stage-0": "^6.5.0", "lodash": "^4.15.0", "open-browser-webpack-plugin": "0.0.5", "json-loader": "^0.5.4", - "webpack": "^1.13.2", - "webpack-dev-server": "^1.14.1" + "webpack": "^3.3.0", + "webpack-dev-server": "^2.5.1" }, "dependencies": { "react": "^15.6.1", diff --git a/webpack.config.js b/webpack.config.js index 2c22232..d50e693 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -26,7 +26,7 @@ module.exports = { { test: /\.js$/, exclude: /node_modules/, - loader: 'babel', + loader: 'babel-loader', include: __dirname, query: { presets: ['es2015', 'react', 'stage-0'] From a4886d1ba53a2b56dbcf434d910f3b00f15e7738 Mon Sep 17 00:00:00 2001 From: geobas Date: Mon, 24 Jul 2017 22:00:05 +0300 Subject: [PATCH 06/24] Refactor MainInterface module / Add validation to some form fields --- src/js/AddAppointment.js | 33 ++++++++++++++++++++++++--------- src/js/ErrorMessage.js | 32 ++++++++++++++++++++++++++++++++ src/js/MainInterface.js | 21 ++++++++++++++++----- 3 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/js/ErrorMessage.js diff --git a/src/js/AddAppointment.js b/src/js/AddAppointment.js index cbc6509..8386cf2 100644 --- a/src/js/AddAppointment.js +++ b/src/js/AddAppointment.js @@ -23,14 +23,29 @@ export default class AddAppointment extends Component { } handleAdd(e) { - let tempItem = { - petName: this.refs.inputPetName.value, - ownerName: this.refs.inputOwnerName.value, - aptDate: this.refs.inputAptDate.value + ' ' + this.refs.inputAptTime.value, - aptNotes: this.refs.inputAptNotes.value - }; - e.preventDefault(); - this.props.addApt(tempItem); + let errorMsg = []; + + let petName = this.refs.inputPetName.value; + if ( petName.length < 5 ) errorMsg.push({ petName : 'Pet name is too short...' }); + + let ownerName = this.refs.inputOwnerName.value; + if ( ownerName.length < 5 ) errorMsg.push({ ownerName : 'Owner name is too short...' }); + + if ( errorMsg.length > 0) { + this.props.addErrorMsg(errorMsg, true); + e.preventDefault(); + } else { + let tempItem = { + petName, + ownerName, + aptDate: this.refs.inputAptDate.value + ' ' + this.refs.inputAptTime.value, + aptNotes: this.refs.inputAptNotes.value + }; + e.preventDefault(); + this.props.addApt(tempItem); + this.props.addErrorMsg(errorMsg, false); + this.refs.appointmentForm.reset(); + } } render() { @@ -40,7 +55,7 @@ export default class AddAppointment extends Component { Add Appointment
    -
    +
    diff --git a/src/js/ErrorMessage.js b/src/js/ErrorMessage.js new file mode 100644 index 0000000..f46e77b --- /dev/null +++ b/src/js/ErrorMessage.js @@ -0,0 +1,32 @@ +import React,{Component} from 'react'; + +export default class ErrorMessage extends Component { + + constructor(props) { + super(props); + this.showErrors = this.showErrors.bind(this); + } + + componentWillMount() { + let display = this.props.errorsVisible ? 'block' : 'none'; + this.displayErrors = { display }; + } + + componentWillReceiveProps(nextProps) { + let display = nextProps.errorsVisible ? 'block' : 'none'; + this.displayErrors = { display }; + } + + showErrors(error, i) { + return

    {Object.values(error)[0]}

    ; + } + + render() { + return ( +
    + + { this.props.errors.map(this.showErrors) } +
    + ) + } +} \ No newline at end of file diff --git a/src/js/MainInterface.js b/src/js/MainInterface.js index 6e89e5b..246d362 100644 --- a/src/js/MainInterface.js +++ b/src/js/MainInterface.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import _ from 'lodash'; import AptList from './AptList'; +import ErrorMessage from './ErrorMessage'; import AddAppointment from './AddAppointment'; import SearchAppointments from './SearchAppointments'; @@ -14,11 +15,14 @@ export default class MainInterface extends Component { orderBy: 'petName', orderDir: 'asc', queryText: '', - myAppointments: [] + myAppointments: [], + errorMsg: [], + errorMsgVisible: false }; this.deleteMessage = this.deleteMessage.bind(this); this.toggleAddDisplay = this.toggleAddDisplay.bind(this); this.addItem = this.addItem.bind(this); + this.addError = this.addError.bind(this); this.reOrder = this.reOrder.bind(this); this.setQueryText = this.setQueryText.bind(this); this.showAppointments = this.showAppointments.bind(this); @@ -48,6 +52,11 @@ export default class MainInterface extends Component { this.setState({ myAppointments: tempApts }); } + addError(errorItems, visible) { + this.setState({ errorMsg: errorItems }); + this.setState({ errorMsgVisible: visible }); + } + reOrder(orderBy, orderDir) { this.setState({ orderBy, orderDir }); } @@ -87,15 +96,17 @@ export default class MainInterface extends Component { } render() { - - let filteredApts = this.showAppointments(); - return (
    + -
      { filteredApts }
    +
      { this.showAppointments() }
    ) } From 381234f589e195d4ca6ee03453be3aef0a8fdbef Mon Sep 17 00:00:00 2001 From: geobas Date: Mon, 24 Jul 2017 22:16:40 +0300 Subject: [PATCH 07/24] Add Webshim polyfill for date & time input fields --- build/index.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/index.html b/build/index.html index a3e0e27..09e3ae4 100644 --- a/build/index.html +++ b/build/index.html @@ -49,6 +49,12 @@ + + \ No newline at end of file From 2799a1bb25695fc89e0600f8f352fe1ec4a55688 Mon Sep 17 00:00:00 2001 From: geobas Date: Tue, 25 Jul 2017 20:41:33 +0300 Subject: [PATCH 08/24] Add more validation --- src/js/AddAppointment.js | 12 +++++++++--- src/js/ErrorMessage.js | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/js/AddAppointment.js b/src/js/AddAppointment.js index 8386cf2..5412f18 100644 --- a/src/js/AddAppointment.js +++ b/src/js/AddAppointment.js @@ -25,12 +25,18 @@ export default class AddAppointment extends Component { handleAdd(e) { let errorMsg = []; - let petName = this.refs.inputPetName.value; + const petName = this.refs.inputPetName.value; if ( petName.length < 5 ) errorMsg.push({ petName : 'Pet name is too short...' }); - let ownerName = this.refs.inputOwnerName.value; + const ownerName = this.refs.inputOwnerName.value; if ( ownerName.length < 5 ) errorMsg.push({ ownerName : 'Owner name is too short...' }); + const aptDate = this.refs.inputAptDate.value; + if ( !aptDate ) errorMsg.push({ inputAptDate : 'Date was not given...' }); + + const aptTime = this.refs.inputAptTime.value; + if ( !aptTime ) errorMsg.push({ aptTime : 'Time was not given...' }); + if ( errorMsg.length > 0) { this.props.addErrorMsg(errorMsg, true); e.preventDefault(); @@ -38,7 +44,7 @@ export default class AddAppointment extends Component { let tempItem = { petName, ownerName, - aptDate: this.refs.inputAptDate.value + ' ' + this.refs.inputAptTime.value, + aptDate: aptDate + ' ' + aptTime, aptNotes: this.refs.inputAptNotes.value }; e.preventDefault(); diff --git a/src/js/ErrorMessage.js b/src/js/ErrorMessage.js index f46e77b..5ef312e 100644 --- a/src/js/ErrorMessage.js +++ b/src/js/ErrorMessage.js @@ -23,8 +23,7 @@ export default class ErrorMessage extends Component { render() { return ( -
    - +
    { this.props.errors.map(this.showErrors) }
    ) From d9b2c654697fc21371fc6d166afe75087d015c42 Mon Sep 17 00:00:00 2001 From: geobas Date: Tue, 25 Jul 2017 21:47:31 +0300 Subject: [PATCH 09/24] Setup Karma with Mocha.js and expect / Add test cases for ErrorMessage module --- karma.conf.js | 53 ++++++++++++++++++++++++++++++++++ package.json | 13 +++++++-- src/tests/ErrorMessage-test.js | 37 ++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 karma.conf.js create mode 100644 src/tests/ErrorMessage-test.js diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..9a9a5f5 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,53 @@ +var webpackConfig = require('./webpack.config.js'); + +module.exports = function(config) { + + config.set({ + + basePath: '', + + frameworks: ['mocha'], + + files: [ + 'src/tests/*.js' + ], + + preprocessors: { + 'src/tests/*.js': ['webpack'], + }, + + webpack: webpackConfig, + + reporters: ['mocha'], + + mochaReporter: { + colors: { + success: 'bgGreen', + info: 'blue', + warning: 'orange', + error: 'bgRed' + }, + symbols: { + success: '+', + info: '#', + warning: '!', + error: 'x' + } + }, + + port: 9876, + + colors: true, + + logLevel: config.LOG_INFO, + + autoWatch: true, + + browsers: ['Chrome', 'Firefox'], + + singleRun: false, + + concurrency: Infinity + }) + +} \ No newline at end of file diff --git a/package.json b/package.json index b03cbb7..aee198e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Project for Building an Interface with React Course on Lynda.com() => ES6 Port", "scripts": { "start": "./node_modules/.bin/webpack-dev-server --progress --watch-poll", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "karma start" }, "repository": { "type": "git", @@ -22,7 +22,16 @@ "open-browser-webpack-plugin": "0.0.5", "json-loader": "^0.5.4", "webpack": "^3.3.0", - "webpack-dev-server": "^2.5.1" + "webpack-dev-server": "^2.5.1", + "karma": "^1.7.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "~1.0.1", + "karma-cli": "1.0.1", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.3", + "karma-webpack": "^2.0.4", + "mocha": "^3.4.2", + "expect": "^1.20.2" }, "dependencies": { "react": "^15.6.1", diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js new file mode 100644 index 0000000..8b82a99 --- /dev/null +++ b/src/tests/ErrorMessage-test.js @@ -0,0 +1,37 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import ErrorMessage from '../js/ErrorMessage.js'; +import ReactTestUtils from 'react-dom/test-utils'; +import expect, { createSpy, spyOn, isSpy } from 'expect'; + +describe('ErrorMessage', function () { + + it('loads without error', function () { + var errorMessage = ReactTestUtils.renderIntoDocument( + + ); + + expect(errorMessage).toExist(); + expect(errorMessage.props.errors.length).toEqual(0); + }); + + it('shows error messages', function () { + var errorMessage = ReactTestUtils.renderIntoDocument( + + ); + + const errors = errorMessage.props.errors; + + expect(errors.length).toEqual(2); + expect(errors[1]).toEqual({'error2': 'dummy2'}); + expect(Object.values(errors[1])[0]).toEqual('dummy2'); + expect(errorMessage.props.errorsVisible).toEqual(true); + }); + +}); \ No newline at end of file From 04b38b179751c034f4f98b84f40292a430999dfb Mon Sep 17 00:00:00 2001 From: geobas Date: Tue, 25 Jul 2017 21:56:20 +0300 Subject: [PATCH 10/24] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e8bb8e..1f49031 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Building an Interface with React -This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). +This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). The code is converted from ES5 to ES6 and gulp is replaced with webpack. ## Instructions @@ -9,8 +9,10 @@ This repository has branches for each of the videos in the course. You can use t - [node.js](http://nodejs.org/) - [git](http://git-scm.com/) - [webpack](http://webpack.js.org/) + - [karma](http://karma-runner.github.io/) 2. Clone this repository into your local machine using the terminal (mac) or Gitbash (PC) `> git clone https://github.com/geobas/reactinterface` 3. CD to the folder `cd reactinterface` 4. Run `> npm install` to install the project dependencies 5. Run `> npm start` command to start webpack-dev-server -6. Build something awesome +6. Run `> npm test` command to start Karma +7. Build something awesome \ No newline at end of file From 414de9517f677e493300ed8de82388b24bf0ee4a Mon Sep 17 00:00:00 2001 From: geobas Date: Fri, 28 Jul 2017 20:32:55 +0300 Subject: [PATCH 11/24] Remove expect / add chai.js & Enzyme / Add more assertions for testing ErrorMessage module --- karma.conf.js | 2 +- package.json | 78 +++++++++++++++++----------------- src/js/ErrorMessage.js | 2 +- src/tests/ErrorMessage-test.js | 33 +++++++++++--- webpack.config.js | 8 ++++ 5 files changed, 76 insertions(+), 47 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 9a9a5f5..724caa7 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -43,7 +43,7 @@ module.exports = function(config) { autoWatch: true, - browsers: ['Chrome', 'Firefox'], + // browsers: ['Chrome', 'Firefox'], singleRun: false, diff --git a/package.json b/package.json index aee198e..a2a7b85 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,42 @@ { - "name": "reatInterface", - "version": "0.0.1", - "description": "Project for Building an Interface with React Course on Lynda.com() => ES6 Port", - "scripts": { - "start": "./node_modules/.bin/webpack-dev-server --progress --watch-poll", - "test": "karma start" - }, - "repository": { - "type": "git", - "url": "https://github.com/geobas/reactinterface" - }, - "author": "Geo Bas", - "devDependencies": { - "babel-cli": "^6.24.1", - "babel-core": "^6.25.0", - "babel-loader": "^7.1.1", - "babel-preset-es2015": "^6.13.2", - "babel-preset-react": "^6.11.1", - "babel-preset-stage-0": "^6.5.0", - "lodash": "^4.15.0", - "open-browser-webpack-plugin": "0.0.5", - "json-loader": "^0.5.4", - "webpack": "^3.3.0", - "webpack-dev-server": "^2.5.1", - "karma": "^1.7.0", - "karma-chrome-launcher": "^2.2.0", - "karma-firefox-launcher": "~1.0.1", - "karma-cli": "1.0.1", - "karma-mocha": "^1.3.0", - "karma-mocha-reporter": "^2.2.3", - "karma-webpack": "^2.0.4", - "mocha": "^3.4.2", - "expect": "^1.20.2" - }, - "dependencies": { - "react": "^15.6.1", - "react-dom": "^15.6.1" - } + "name": "reatInterface", + "version": "0.0.1", + "description": "Project for Building an Interface with React Course on Lynda.com() => ES6 Port", + "scripts": { + "start": "./node_modules/.bin/webpack-dev-server --progress --watch-poll", + "test": "karma start" + }, + "repository": { + "type": "git", + "url": "https://github.com/geobas/reactinterface" + }, + "author": "Geo Bas", + "devDependencies": { + "babel-cli": "^6.24.1", + "babel-core": "^6.25.0", + "babel-loader": "^7.1.1", + "babel-preset-es2015": "^6.13.2", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-0": "^6.5.0", + "lodash": "^4.15.0", + "open-browser-webpack-plugin": "0.0.5", + "json-loader": "^0.5.4", + "webpack": "^3.3.0", + "webpack-dev-server": "^2.5.1", + "karma": "^1.7.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "~1.0.1", + "karma-cli": "1.0.1", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.3", + "karma-webpack": "^2.0.4", + "mocha": "^3.4.2", + "chai": "^4.1.0", + "enzyme": "^2.9.1", + "chai-enzyme": "^0.8.0" + }, + "dependencies": { + "react": "^15.6.1", + "react-dom": "^15.6.1" + } } diff --git a/src/js/ErrorMessage.js b/src/js/ErrorMessage.js index 5ef312e..53bdf7a 100644 --- a/src/js/ErrorMessage.js +++ b/src/js/ErrorMessage.js @@ -18,7 +18,7 @@ export default class ErrorMessage extends Component { } showErrors(error, i) { - return

    {Object.values(error)[0]}

    ; + return

    {Object.values(error)[0]}

    ; } render() { diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js index 8b82a99..9e1e9ac 100644 --- a/src/tests/ErrorMessage-test.js +++ b/src/tests/ErrorMessage-test.js @@ -2,7 +2,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ErrorMessage from '../js/ErrorMessage.js'; import ReactTestUtils from 'react-dom/test-utils'; -import expect, { createSpy, spyOn, isSpy } from 'expect'; +import chai, { expect } from 'chai'; +import { shallow, mount, render } from 'enzyme'; + +/* chai-enzyme imports */ +// import chai from 'chai' +import chaiEnzyme from 'chai-enzyme' +chai.use(chaiEnzyme()); describe('ErrorMessage', function () { @@ -14,8 +20,9 @@ describe('ErrorMessage', function () { /> ); - expect(errorMessage).toExist(); - expect(errorMessage.props.errors.length).toEqual(0); + expect(errorMessage).to.exist; + expect(errorMessage.props.errors.length).to.equal(0); + expect(errorMessage.props.errorsVisible).to.be.false; }); it('shows error messages', function () { @@ -26,12 +33,24 @@ describe('ErrorMessage', function () { /> ); + // Chai.js assertions + expect(errorMessage).to.be.instanceOf(ErrorMessage); const errors = errorMessage.props.errors; + expect(errors).to.not.be.undefined; + expect(errors.length).to.equal(2, 'errors length fault'); + expect(errors[1]).to.have.property('error2': 'dummy2'); + expect(Object.values(errors[1])[0]).to.equal('dummy2'); + expect(errorMessage.props.errorsVisible).to.not.be.undefined; + expect(errorMessage.props.errorsVisible).to.equal(true, 'errors div tag should be visible'); - expect(errors.length).toEqual(2); - expect(errors[1]).toEqual({'error2': 'dummy2'}); - expect(Object.values(errors[1])[0]).toEqual('dummy2'); - expect(errorMessage.props.errorsVisible).toEqual(true); + // Chai.js assertions for enzyme + const wrapper = mount(); + expect(wrapper.find('.error')).to.have.length(2); + expect(wrapper.find('p').first()).to.have.className('error'); + expect(wrapper.find('p').first().text()).to.equal('dummy1'); }); }); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index d50e693..048600a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -38,6 +38,14 @@ module.exports = { // loader: 'json-loader' // }, ] + }, + + // required by enzyme + externals: { + 'react/addons': true, + 'react/lib/ExecutionEnvironment': true, + 'react/lib/ReactContext': true, + 'react-addons-test-utils': 'react-dom', } } \ No newline at end of file From eac7b9f380c67d6789e08c015288fd636bdd63cc Mon Sep 17 00:00:00 2001 From: geobas Date: Sun, 30 Jul 2017 21:39:25 +0300 Subject: [PATCH 12/24] Add test cases for AptList module --- package.json | 1 + src/tests/AptList-test.js | 75 ++++++++++++++++++++++++++++++++++ src/tests/ErrorMessage-test.js | 66 +++++++++++++++--------------- 3 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 src/tests/AptList-test.js diff --git a/package.json b/package.json index a2a7b85..7129739 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "karma-webpack": "^2.0.4", "mocha": "^3.4.2", "chai": "^4.1.0", + "chai-spies": "^0.7.1", "enzyme": "^2.9.1", "chai-enzyme": "^0.8.0" }, diff --git a/src/tests/AptList-test.js b/src/tests/AptList-test.js new file mode 100644 index 0000000..8baae85 --- /dev/null +++ b/src/tests/AptList-test.js @@ -0,0 +1,75 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import AptList from '../js/AptList.js'; +import ReactTestUtils from 'react-dom/test-utils'; +import chai, { expect } from 'chai'; +import spies from 'chai-spies'; +chai.use(spies); +import { shallow, mount, render } from 'enzyme'; + +/* chai-enzyme imports */ +// import chai from 'chai' +import chaiEnzyme from 'chai-enzyme' +chai.use(chaiEnzyme()); + +describe('AptList', function () { + + it('loads without error', function () { + const aptList = ReactTestUtils.renderIntoDocument( + + ); + + expect(aptList).to.exist; + expect(aptList.props.singleItem).to.equal(0); + expect(aptList.props.whichItem).to.equal(0); + }); + + it('shows a single appointment', function () { + const aptList = ReactTestUtils.renderIntoDocument( + + ); + + // Chai.js assertions + expect(aptList).to.be.instanceOf(AptList); + const singleItem = aptList.props.singleItem; + expect(singleItem).to.not.be.undefined; + expect(singleItem).to.have.property('petName': 'Buffy'); + expect(singleItem).to.have.property('ownerName').that.is.a('String'); + expect(singleItem).to.have.property('aptDate').that.is.a('String'); + const whichItem = aptList.props.whichItem; + expect(whichItem).to.not.be.undefined; + expect(whichItem).to.have.property('ownerName': 'Hassum Harrod'); + + // const spy = chai.spy(aptList); + + // const btn = ReactTestUtils.findRenderedDOMComponentWithClass( + // aptList, 'pet-delete' + // ); + // ReactTestUtils.Simulate.click(btn); + + + + + // Chai.js assertions for enzyme + const wrapper = mount( + + ); + const callback = chai.spy(wrapper, 'handleDelete'); + expect(wrapper.find('.pet-name').text()).to.equal('Buffy'); + expect(wrapper.find('.apt-notes').text()).to.equal('This Chihuahua has not eaten for three days and is lethargic'); + wrapper.find('.pet-delete').get(0).click(); + // expect(callback).to.have.been.called().with({ petName: "Buffy", ownerName: "Hassum Harrod", aptDate: "2016-06-20 15:30", aptNotes: "This Chihuahua has not eaten for three days and is lethargic" }); + + }); + +}); \ No newline at end of file diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js index 9e1e9ac..530f3c0 100644 --- a/src/tests/ErrorMessage-test.js +++ b/src/tests/ErrorMessage-test.js @@ -1,5 +1,4 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import ErrorMessage from '../js/ErrorMessage.js'; import ReactTestUtils from 'react-dom/test-utils'; import chai, { expect } from 'chai'; @@ -12,45 +11,48 @@ chai.use(chaiEnzyme()); describe('ErrorMessage', function () { - it('loads without error', function () { - var errorMessage = ReactTestUtils.renderIntoDocument( - - ); + /> + ); - expect(errorMessage).to.exist; - expect(errorMessage.props.errors.length).to.equal(0); - expect(errorMessage.props.errorsVisible).to.be.false; - }); + expect(errorMessage).to.exist; + expect(errorMessage.props.errors.length).to.equal(0); + expect(errorMessage.props.errorsVisible).to.be.false; + }); - it('shows error messages', function () { - var errorMessage = ReactTestUtils.renderIntoDocument( - - ); - - // Chai.js assertions - expect(errorMessage).to.be.instanceOf(ErrorMessage); - const errors = errorMessage.props.errors; - expect(errors).to.not.be.undefined; - expect(errors.length).to.equal(2, 'errors length fault'); - expect(errors[1]).to.have.property('error2': 'dummy2'); - expect(Object.values(errors[1])[0]).to.equal('dummy2'); - expect(errorMessage.props.errorsVisible).to.not.be.undefined; - expect(errorMessage.props.errorsVisible).to.equal(true, 'errors div tag should be visible'); - - // Chai.js assertions for enzyme - const wrapper = mount(); + /> + ); + + // Chai.js assertions + expect(errorMessage).to.be.instanceOf(ErrorMessage); + const errors = errorMessage.props.errors; + expect(errors).to.not.be.undefined; + expect(errors.length).to.equal(2, 'errors length fault'); + expect(errors[1]).to.have.property('error2': 'dummy2'); + expect(Object.values(errors[1])[0]).to.equal('dummy2'); + expect(errorMessage.props.errorsVisible).to.not.be.undefined; + expect(errorMessage.props.errorsVisible).to.equal(true, 'errors div tag should be visible'); + + // Chai.js assertions for enzyme + const wrapper = mount( + + ); expect(wrapper.find('.error')).to.have.length(2); expect(wrapper.find('p').first()).to.have.className('error'); expect(wrapper.find('p').first().text()).to.equal('dummy1'); - }); + expect(wrapper).to.have.style('display', 'block'); + }); }); \ No newline at end of file From b8cb352e1770bfe8ba9f4cb9a40254a2c02a56c7 Mon Sep 17 00:00:00 2001 From: geobas Date: Tue, 1 Aug 2017 20:17:44 +0300 Subject: [PATCH 13/24] Add test cases for SearchAppointments & AddAppointment modules --- package.json | 7 +- src/js/SearchAppointments.js | 12 +-- src/tests/AddAppointment-test.js | 76 +++++++++++++++++++ src/tests/AptList-test.js | 40 +++++----- src/tests/ErrorMessage-test.js | 0 src/tests/SearchAppointments-test.js | 105 +++++++++++++++++++++++++++ webpack.config.js | 10 ++- 7 files changed, 223 insertions(+), 27 deletions(-) create mode 100755 src/tests/AddAppointment-test.js mode change 100644 => 100755 src/tests/AptList-test.js mode change 100644 => 100755 src/tests/ErrorMessage-test.js create mode 100755 src/tests/SearchAppointments-test.js diff --git a/package.json b/package.json index 7129739..02fcc27 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,12 @@ "karma-webpack": "^2.0.4", "mocha": "^3.4.2", "chai": "^4.1.0", - "chai-spies": "^0.7.1", "enzyme": "^2.9.1", - "chai-enzyme": "^0.8.0" + "chai-enzyme": "^0.8.0", + "react-test-renderer": "^15.6.1", + "sinon": "^2.4.1", + "sinon-chai": "^2.12.0", + "jquery": "~1.11.1" }, "dependencies": { "react": "^15.6.1", diff --git a/src/js/SearchAppointments.js b/src/js/SearchAppointments.js index 80c808b..82d8d09 100644 --- a/src/js/SearchAppointments.js +++ b/src/js/SearchAppointments.js @@ -28,21 +28,21 @@ export default class SearchAppointments extends Component {
    - + diff --git a/src/tests/AddAppointment-test.js b/src/tests/AddAppointment-test.js new file mode 100755 index 0000000..64c2d42 --- /dev/null +++ b/src/tests/AddAppointment-test.js @@ -0,0 +1,76 @@ +import React from 'react'; +import AddAppointment from '../js/AddAppointment.js'; +import ReactTestUtils from 'react-dom/test-utils'; +import { shallow, mount, render } from 'enzyme'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import chai, { expect } from 'chai'; +chai.use(sinonChai); +import chaiEnzyme from 'chai-enzyme' +chai.use(chaiEnzyme()); + +describe('AddAppointment', function () { + + it('loads without error', function () { + const addAppointment = ReactTestUtils.renderIntoDocument( + + ); + + expect(addAppointment).to.exist; + expect(addAppointment.props.bodyVisible).to.equal(false); + expect(addAppointment.props.handleToggle).to.equal(null); + expect(addAppointment.props.addApt).to.equal(null); + expect(addAppointment.props.addErrorMsg).to.equal(null); + }); + + it('adds appointment', function () { + + // Chai.js assertions for enzyme + const handleToggle = sinon.stub(); + const addApt = sinon.stub(); + const addErrorMsg = sinon.stub(); + + const wrapper = mount( + + ); + + expect(wrapper.find('.panel-body')).to.have.style('display', 'none'); + wrapper.find('.apt-addheading').simulate('click'); + expect(handleToggle).to.have.been.calledOnce; + wrapper.find('.panel-body').get(0).style.display = 'block'; + expect(wrapper.find('.panel-body')).to.have.style('display', 'block'); + + // all fields are empty + wrapper.find('.add-appointment').simulate('submit'); + expect(addErrorMsg).to.have.been.calledOnce; + expect(addErrorMsg).to.have.been.calledWithExactly([{ petName : 'Pet name is too short...' }, { ownerName : 'Owner name is too short...' }, { inputAptDate : 'Date was not given...' }, { aptTime : 'Time was not given...' }], true); + + // some fields are empty + // wrapper.find('#petName').node.value = 'dummy'; // it works also + wrapper.find('#petName').get(0).value = 'dummy'; + wrapper.find('.add-appointment').simulate('submit'); + expect(addErrorMsg).to.have.been.calledWithExactly([{ ownerName : 'Owner name is too short...' }, { inputAptDate : 'Date was not given...' }, { aptTime : 'Time was not given...' }], true); + + // no field is empty + wrapper.find('#petOwner').get(0).value = 'dummy'; + wrapper.find('#aptDate').get(0).value = 'date'; + wrapper.find('#aptTime').get(0).value = 'time'; + wrapper.find('.add-appointment').simulate('submit'); + expect(addErrorMsg).to.have.been.calledThrice; + expect(addErrorMsg).to.have.been.calledWithExactly([], false); + expect(addApt).to.have.been.calledOnce; + expect(addApt).to.have.been.calledWithExactly({ petName: 'dummy', ownerName: 'dummy', aptDate: 'date time', aptNotes: '' }); + + }); + +}); \ No newline at end of file diff --git a/src/tests/AptList-test.js b/src/tests/AptList-test.js old mode 100644 new mode 100755 index 8baae85..21508c2 --- a/src/tests/AptList-test.js +++ b/src/tests/AptList-test.js @@ -3,8 +3,9 @@ import ReactDOM from 'react-dom'; import AptList from '../js/AptList.js'; import ReactTestUtils from 'react-dom/test-utils'; import chai, { expect } from 'chai'; -import spies from 'chai-spies'; -chai.use(spies); +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +chai.use(sinonChai); import { shallow, mount, render } from 'enzyme'; /* chai-enzyme imports */ @@ -28,10 +29,15 @@ describe('AptList', function () { }); it('shows a single appointment', function () { + + let itemReturned; + const getItemToDelete = item => { itemReturned = item; } + const aptList = ReactTestUtils.renderIntoDocument( ); @@ -39,37 +45,35 @@ describe('AptList', function () { expect(aptList).to.be.instanceOf(AptList); const singleItem = aptList.props.singleItem; expect(singleItem).to.not.be.undefined; - expect(singleItem).to.have.property('petName': 'Buffy'); + expect(singleItem).to.have.property('petName'); expect(singleItem).to.have.property('ownerName').that.is.a('String'); expect(singleItem).to.have.property('aptDate').that.is.a('String'); const whichItem = aptList.props.whichItem; expect(whichItem).to.not.be.undefined; - expect(whichItem).to.have.property('ownerName': 'Hassum Harrod'); - - // const spy = chai.spy(aptList); - - // const btn = ReactTestUtils.findRenderedDOMComponentWithClass( - // aptList, 'pet-delete' - // ); - // ReactTestUtils.Simulate.click(btn); - - + expect(whichItem).to.have.property('ownerName').equal('Hassum Harrod'); + const btn = ReactTestUtils.findRenderedDOMComponentWithClass( + aptList, 'pet-delete' + ); + ReactTestUtils.Simulate.click(btn); + expect(singleItem).to.have.property('petName').to.equal('Buffy'); + expect(itemReturned).to.have.property('petName').that.is.a('String'); // Chai.js assertions for enzyme + const deleteItem = sinon.stub(); const wrapper = mount( ); - const callback = chai.spy(wrapper, 'handleDelete'); + expect(wrapper.find('.pet-name').text()).to.equal('Buffy'); expect(wrapper.find('.apt-notes').text()).to.equal('This Chihuahua has not eaten for three days and is lethargic'); - wrapper.find('.pet-delete').get(0).click(); - // expect(callback).to.have.been.called().with({ petName: "Buffy", ownerName: "Hassum Harrod", aptDate: "2016-06-20 15:30", aptNotes: "This Chihuahua has not eaten for three days and is lethargic" }); - + wrapper.find('.pet-delete').simulate('click'); + expect(deleteItem).to.have.been.calledOnce; + expect(deleteItem).to.have.been.calledWith({ petName: "Buffy", ownerName: "Hassum Harrod", aptDate: "2016-06-20 15:30", aptNotes: "This Chihuahua has not eaten for three days and is lethargic" }); }); }); \ No newline at end of file diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js old mode 100644 new mode 100755 diff --git a/src/tests/SearchAppointments-test.js b/src/tests/SearchAppointments-test.js new file mode 100755 index 0000000..1108bc0 --- /dev/null +++ b/src/tests/SearchAppointments-test.js @@ -0,0 +1,105 @@ +import React from 'react'; +import SearchAppointments from '../js/SearchAppointments.js'; +import ReactTestUtils from 'react-dom/test-utils'; +import { shallow, mount, render } from 'enzyme'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import chai, { expect } from 'chai'; +chai.use(sinonChai); +import chaiEnzyme from 'chai-enzyme' +chai.use(chaiEnzyme()); + +describe('SearchAppointments', function () { + + it('loads without error', function () { + const searchAppointments = ReactTestUtils.renderIntoDocument( + + ); + + expect(searchAppointments).to.exist; + expect(searchAppointments.props.orderBy).to.equal(null); + expect(searchAppointments.props.orderDir).to.equal(null); + expect(searchAppointments.props.onReOrder).to.equal(null); + expect(searchAppointments.props.onSearch).to.equal(null); + }); + + it('it filters appointments', function () { + + let orderByReturned, orderDirReturned, termReturned; + const getParams = (orderBy, orderDir) => { orderByReturned = orderBy; orderDirReturned = orderDir; }; + const getSearchTerm = term => { termReturned = term; } + + const searchAppointments = ReactTestUtils.renderIntoDocument( + + ); + + // Chai.js assertions + expect(searchAppointments).to.be.instanceOf(SearchAppointments); + expect(searchAppointments.props.orderBy).to.not.be.undefined; + expect(searchAppointments.props.orderBy).to.equal('petName'); + expect(searchAppointments.props.orderDir).to.not.be.undefined; + expect(searchAppointments.props.orderDir).to.equal('asc'); + + // test + let anchor = ReactTestUtils.findRenderedDOMComponentWithClass( + searchAppointments, 'petName' + ); + ReactTestUtils.Simulate.click(anchor); + expect(orderByReturned).to.equal('petName'); + expect(orderDirReturned).to.equal('asc'); + + // another test + anchor = ReactTestUtils.findRenderedDOMComponentWithClass( + searchAppointments, 'ownerName' + ); + ReactTestUtils.Simulate.click(anchor); + expect(orderByReturned).to.equal('ownerName'); + expect(orderDirReturned).to.equal('asc'); + + // another one + anchor = ReactTestUtils.findRenderedDOMComponentWithClass( + searchAppointments, 'desc' + ); + ReactTestUtils.Simulate.click(anchor); + expect(orderByReturned).to.equal('petName'); + expect(orderDirReturned).to.equal('desc'); + + // last test + let input = ReactTestUtils.findRenderedDOMComponentWithClass( + searchAppointments, 'SearchApts' + ); + ReactTestUtils.Simulate.change(input); + expect(termReturned).to.equal(''); + input.value = 'xyz'; + ReactTestUtils.Simulate.change(input); + expect(termReturned).to.equal('xyz'); + + // Chai.js assertions for enzyme + const wrapper = mount( + + ); + wrapper.find('.aptDate').simulate('click'); + expect(orderByReturned).to.equal('aptDate'); + expect(orderDirReturned).to.equal('asc'); + + wrapper.find('.SearchApts').simulate('change'); + expect(termReturned).to.equal(''); + + }); + +}); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 048600a..428ed63 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); const OpenBrowserPlugin = require('open-browser-webpack-plugin'); +const webpack = require('webpack'); module.exports = { @@ -46,6 +47,13 @@ module.exports = { 'react/lib/ExecutionEnvironment': true, 'react/lib/ReactContext': true, 'react-addons-test-utils': 'react-dom', - } + }, + + plugins: [ + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery" + }) + ] } \ No newline at end of file From 9ac2449d5c5eae5df9e87ec361e2e63630160b3a Mon Sep 17 00:00:00 2001 From: geobas Date: Tue, 1 Aug 2017 20:35:32 +0300 Subject: [PATCH 14/24] Minor fix --- webpack.config.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 428ed63..e7f9347 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,11 @@ module.exports = { }, plugins: [ - new OpenBrowserPlugin({ url: 'http://localhost:3000' }) + new OpenBrowserPlugin({ url: 'http://localhost:3000' }), + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery" + }) ], module: { @@ -33,11 +37,11 @@ module.exports = { presets: ['es2015', 'react', 'stage-0'] } }, - // { - // test: /\.json$/, - // exclude: /(node_modules)/, - // loader: 'json-loader' - // }, + // { + // test: /\.json$/, + // exclude: /(node_modules)/, + // loader: 'json-loader' + // }, ] }, @@ -47,13 +51,6 @@ module.exports = { 'react/lib/ExecutionEnvironment': true, 'react/lib/ReactContext': true, 'react-addons-test-utils': 'react-dom', - }, - - plugins: [ - new webpack.ProvidePlugin({ - $: "jquery", - jQuery: "jquery" - }) - ] + } } \ No newline at end of file From daefde34b517ead1c7b088ad5c205aaaf9c34662 Mon Sep 17 00:00:00 2001 From: geobas Date: Thu, 3 Aug 2017 19:33:39 +0300 Subject: [PATCH 15/24] Various changes - Add test cases for MainInterface module - Update some test cases --- karma.conf.js | 6 +- src/js/AddAppointment.js | 2 +- src/tests/AddAppointment-test.js | 1 + src/tests/AptList-test.js | 0 src/tests/ErrorMessage-test.js | 0 src/tests/MainInterface-test.js | 143 +++++++++++++++++++++++++++ src/tests/SearchAppointments-test.js | 8 +- 7 files changed, 154 insertions(+), 6 deletions(-) mode change 100755 => 100644 src/tests/AddAppointment-test.js mode change 100755 => 100644 src/tests/AptList-test.js mode change 100755 => 100644 src/tests/ErrorMessage-test.js create mode 100644 src/tests/MainInterface-test.js mode change 100755 => 100644 src/tests/SearchAppointments-test.js diff --git a/karma.conf.js b/karma.conf.js index 724caa7..939468f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -47,7 +47,11 @@ module.exports = function(config) { singleRun: false, - concurrency: Infinity + concurrency: Infinity, + + proxies: { + '/assets/data.json': 'http://www.example.com', + } }) } \ No newline at end of file diff --git a/src/js/AddAppointment.js b/src/js/AddAppointment.js index 5412f18..da5221b 100644 --- a/src/js/AddAppointment.js +++ b/src/js/AddAppointment.js @@ -93,7 +93,7 @@ export default class AddAppointment extends Component {
    - +
    diff --git a/src/tests/AddAppointment-test.js b/src/tests/AddAppointment-test.js old mode 100755 new mode 100644 index 64c2d42..eb8af3a --- a/src/tests/AddAppointment-test.js +++ b/src/tests/AddAppointment-test.js @@ -44,6 +44,7 @@ describe('AddAppointment', function () { /> ); + expect(wrapper.prop('bodyVisible')).to.be.false; expect(wrapper.find('.panel-body')).to.have.style('display', 'none'); wrapper.find('.apt-addheading').simulate('click'); expect(handleToggle).to.have.been.calledOnce; diff --git a/src/tests/AptList-test.js b/src/tests/AptList-test.js old mode 100755 new mode 100644 diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js old mode 100755 new mode 100644 diff --git a/src/tests/MainInterface-test.js b/src/tests/MainInterface-test.js new file mode 100644 index 0000000..b6c1d6f --- /dev/null +++ b/src/tests/MainInterface-test.js @@ -0,0 +1,143 @@ +import React from 'react'; +import MainInterface from '../js/MainInterface.js'; +import AddAppointment from '../js/AddAppointment.js'; +import ReactTestUtils from 'react-dom/test-utils'; +import { shallow, mount, render } from 'enzyme'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import chai, { expect } from 'chai'; +chai.use(sinonChai); +import chaiEnzyme from 'chai-enzyme' +chai.use(chaiEnzyme()); + +describe('MainInterface', function () { + + let wrapper; + + beforeEach(function() { + + wrapper = mount( + + ); + + wrapper.setState({ + aptBodyVisible: false, + orderBy: 'petName', + orderDir: 'asc', + queryText: '', + myAppointments: [ + { + "petName": "Buffy", + "ownerName": "Hassum Harrod", + "aptDate": "2016-06-20 15:30", + "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" + }, + { + "petName": "Spot", + "ownerName": "Constance Smith", + "aptDate": "2016-06-24 08:30", + "aptNotes": "This German Shepherd is having some back pain" + } + ], + errorMsg: [], + errorMsgVisible: false + }); + + }); + + it('loads without error', function () { + + expect(wrapper.find('.interface').exists()).to.be.true; + }); + + it('shows appointments', function () { + + // console.log(wrapper.debug()); + expect(wrapper.state('orderBy')).to.equal('petName'); + expect(wrapper.state('errorMsgVisible')).to.be.false; + expect(wrapper.find('.panel-body')).to.have.style('display', 'none'); + expect(wrapper.find('.media-list').children()).to.have.length(2); + expect(wrapper.find('.media-list').children().first().find('.pet-name').text()).to.equal('Buffy'); + expect(wrapper.find('.media-list').children().last().find('.apt-notes').text()).to.equal('This German Shepherd is having some back pain'); + + wrapper.setState({ aptBodyVisible: true }); + expect(wrapper.find('.panel-body')).to.have.style('display', 'block'); + + wrapper.find('.apt-addheading').simulate('click'); + expect(wrapper.find('.panel-body')).to.have.style('display', 'none'); + + }); + + it('adds a new appointment', function () { + + wrapper.setState({ aptBodyVisible: true }); + expect(wrapper.find('.panel-body')).to.have.style('display', 'block'); + expect(wrapper.find('.media-list li').length).to.equal(2); + + wrapper.find('#petName').get(0).value = 'dummy'; + wrapper.find('#petOwner').get(0).value = 'dummy'; + wrapper.find('#aptDate').get(0).value = '2017-08-02'; + wrapper.find('#aptTime').get(0).value = '14:06'; + wrapper.find('.btn-add-appointment').simulate('submit'); + expect(wrapper.find('.media-list li').length).to.equal(3); + expect(wrapper.contains(dummy)).to.be.true; + expect(wrapper.contains(2017-08-02 14:06)).to.be.true; + wrapper.setState({ aptBodyVisible: false }); + + }); + + it('doesn\'t add a new appointment', function () { + + wrapper.setState({ aptBodyVisible: true }); + expect(wrapper.find('.panel-body')).to.have.style('display', 'block'); + expect(wrapper.find('.media-list li').length).to.equal(2); + + wrapper.find('#petName').get(0).value = 'dummy'; + wrapper.find('.btn-add-appointment').simulate('submit'); + expect(wrapper.find('.media-list li').length).to.equal(2); + expect(wrapper.find('.alert-warning')).to.have.style('display', 'block'); + expect(wrapper.state('errorMsg')).to.be.an('array').that.is.not.empty; + expect(wrapper.state('errorMsg')).to.be.an('array').to.have.lengthOf(3); + expect(wrapper.contains(

    Owner name is too short...

    )).to.equal(true); + + }); + + it('deletes an appointment', function () { + + expect(wrapper.find('.media-list li').length).to.equal(2); + wrapper.find('.pet-delete').first().simulate('click'); + expect(wrapper.find('.media-list li').length).to.equal(1); + + }); + + it('searches an appointment', function () { + + expect(wrapper.find('.media-list li').length).to.equal(2); + wrapper.find('.SearchApts').get(0).value = 'dummy'; + wrapper.find('.SearchApts').first().simulate('change'); + expect(wrapper.find('.media-list li').length).to.equal(0); + + wrapper.find('.SearchApts').node.value = 'smith'; + wrapper.find('.SearchApts').first().simulate('change'); + expect(wrapper.find('.media-list li').length).to.equal(1); + + wrapper.find('.SearchApts').node.value = '08:30'; + wrapper.find('.SearchApts').first().simulate('change'); + expect(wrapper.find('.media-list li').length).to.equal(1); + + }); + + it('sorts appointments', function () { + + expect(wrapper.find('.pet-name').first().text()).to.equal('Buffy'); + wrapper.find('.desc').first().simulate('click'); + expect(wrapper.find('.pet-name').first().text()).to.equal('Spot'); + + wrapper.find('.asc').first().simulate('click'); + wrapper.find('.aptDate').first().simulate('click'); + expect(wrapper.find('.apt-date').last().text()).to.equal('2016-06-24 08:30'); + + }); + + +}); \ No newline at end of file diff --git a/src/tests/SearchAppointments-test.js b/src/tests/SearchAppointments-test.js old mode 100755 new mode 100644 index 1108bc0..14dcccc --- a/src/tests/SearchAppointments-test.js +++ b/src/tests/SearchAppointments-test.js @@ -27,7 +27,7 @@ describe('SearchAppointments', function () { expect(searchAppointments.props.onReOrder).to.equal(null); expect(searchAppointments.props.onSearch).to.equal(null); }); - + it('it filters appointments', function () { let orderByReturned, orderDirReturned, termReturned; @@ -47,7 +47,7 @@ describe('SearchAppointments', function () { expect(searchAppointments).to.be.instanceOf(SearchAppointments); expect(searchAppointments.props.orderBy).to.not.be.undefined; expect(searchAppointments.props.orderBy).to.equal('petName'); - expect(searchAppointments.props.orderDir).to.not.be.undefined; + expect(searchAppointments.props.orderDir).to.not.be.undefined; expect(searchAppointments.props.orderDir).to.equal('asc'); // test @@ -64,7 +64,7 @@ describe('SearchAppointments', function () { ); ReactTestUtils.Simulate.click(anchor); expect(orderByReturned).to.equal('ownerName'); - expect(orderDirReturned).to.equal('asc'); + expect(orderDirReturned).to.equal('asc'); // another one anchor = ReactTestUtils.findRenderedDOMComponentWithClass( @@ -100,6 +100,6 @@ describe('SearchAppointments', function () { wrapper.find('.SearchApts').simulate('change'); expect(termReturned).to.equal(''); - }); + }); }); \ No newline at end of file From ebf00f9f1ce969ed4011b668c9b124c2cd22b1ff Mon Sep 17 00:00:00 2001 From: geobas Date: Thu, 3 Aug 2017 21:04:55 +0300 Subject: [PATCH 16/24] Add PhantomJS --- karma.conf.js | 8 +++++--- package.json | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 939468f..1a8fca0 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,4 +1,5 @@ var webpackConfig = require('./webpack.config.js'); +process.env.PHANTOMJS_BIN = './node_modules/.bin/phantomjs'; module.exports = function(config) { @@ -6,10 +7,11 @@ module.exports = function(config) { basePath: '', - frameworks: ['mocha'], + frameworks: ['mocha', 'es6-shim'], files: [ - 'src/tests/*.js' + 'src/tests/*.js', + 'node_modules/es6-shim/es6-shim.js' ], preprocessors: { @@ -43,7 +45,7 @@ module.exports = function(config) { autoWatch: true, - // browsers: ['Chrome', 'Firefox'], + browsers: ['PhantomJS'], singleRun: false, diff --git a/package.json b/package.json index 02fcc27..3e0e179 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,11 @@ "karma-cli": "1.0.1", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.3", + "karma-phantomjs-launcher": "^1.0.4", "karma-webpack": "^2.0.4", + "phantom": "^4.0.5", + "es6-shim": "^0.35.3", + "karma-es6-shim": "^1.0.0", "mocha": "^3.4.2", "chai": "^4.1.0", "enzyme": "^2.9.1", From 7c70a78aca64dd0f3055c03b1b99ec4d03807340 Mon Sep 17 00:00:00 2001 From: geobas Date: Thu, 3 Aug 2017 21:49:32 +0300 Subject: [PATCH 17/24] Update test case for PhantomJS compatibility --- src/js/ErrorMessage.js | 2 +- src/tests/ErrorMessage-test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 src/tests/ErrorMessage-test.js diff --git a/src/js/ErrorMessage.js b/src/js/ErrorMessage.js index 53bdf7a..c935102 100644 --- a/src/js/ErrorMessage.js +++ b/src/js/ErrorMessage.js @@ -18,7 +18,7 @@ export default class ErrorMessage extends Component { } showErrors(error, i) { - return

    {Object.values(error)[0]}

    ; + return

    { Object.keys(error).map(k => error[k]) }

    ; } render() { diff --git a/src/tests/ErrorMessage-test.js b/src/tests/ErrorMessage-test.js old mode 100644 new mode 100755 index 530f3c0..f83c11d --- a/src/tests/ErrorMessage-test.js +++ b/src/tests/ErrorMessage-test.js @@ -38,7 +38,7 @@ describe('ErrorMessage', function () { expect(errors).to.not.be.undefined; expect(errors.length).to.equal(2, 'errors length fault'); expect(errors[1]).to.have.property('error2': 'dummy2'); - expect(Object.values(errors[1])[0]).to.equal('dummy2'); + expect(errors[1]['error2']).to.equal('dummy2'); expect(errorMessage.props.errorsVisible).to.not.be.undefined; expect(errorMessage.props.errorsVisible).to.equal(true, 'errors div tag should be visible'); From d1e210a2fc2050d97762e7bf5e9f47180baba4cd Mon Sep 17 00:00:00 2001 From: geobas Date: Fri, 11 Aug 2017 23:03:34 +0300 Subject: [PATCH 18/24] Minor fixes --- src/tests/AddAppointment-test.js | 6 +++--- src/tests/AptList-test.js | 2 +- src/tests/MainInterface-test.js | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) mode change 100644 => 100755 src/tests/AddAppointment-test.js mode change 100644 => 100755 src/tests/AptList-test.js mode change 100644 => 100755 src/tests/MainInterface-test.js diff --git a/src/tests/AddAppointment-test.js b/src/tests/AddAppointment-test.js old mode 100644 new mode 100755 index eb8af3a..4b1c34f --- a/src/tests/AddAppointment-test.js +++ b/src/tests/AddAppointment-test.js @@ -31,9 +31,9 @@ describe('AddAppointment', function () { it('adds appointment', function () { // Chai.js assertions for enzyme - const handleToggle = sinon.stub(); - const addApt = sinon.stub(); - const addErrorMsg = sinon.stub(); + const handleToggle = sinon.spy(); + const addApt = sinon.spy(); + const addErrorMsg = sinon.spy(); const wrapper = mount( Date: Sat, 12 Aug 2017 20:07:53 +0300 Subject: [PATCH 19/24] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f49031..ee785a5 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Building an Interface with React This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). -The code is converted from ES5 to ES6 and gulp is replaced with webpack. +The code is converted from ES5 to ES6, gulp is replaced with webpack and all React components are tested. ## Instructions This repository has branches for each of the videos in the course. You can use the branch pop up menu in github to switch to a specific branch and take a look at the course at that stage. Or you can simply add `/tree/BRANCH_NAME` to the URL to go to the branch you want to peek at. @@ -15,4 +15,4 @@ This repository has branches for each of the videos in the course. You can use t 4. Run `> npm install` to install the project dependencies 5. Run `> npm start` command to start webpack-dev-server 6. Run `> npm test` command to start Karma -7. Build something awesome \ No newline at end of file +7. Build something awesome From 9fefe0590cd8a766e969ba9e735161ca3613a433 Mon Sep 17 00:00:00 2001 From: geobas Date: Thu, 17 Aug 2017 22:20:26 +0300 Subject: [PATCH 20/24] Create a redux store to manage component's state --- package.json | 4 +++- src/actions.js | 19 +++++++++++++++++++ src/constants.js | 8 ++++++++ src/initialState.json | 28 ++++++++++++++++++++++++++++ src/store/index.js | 36 ++++++++++++++++++++++++++++++++++++ src/store/reducers.js | 42 ++++++++++++++++++++++++++++++++++++++++++ src/test-redux.js | 42 ++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 3 ++- 8 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/actions.js create mode 100644 src/constants.js create mode 100644 src/initialState.json create mode 100644 src/store/index.js create mode 100644 src/store/reducers.js create mode 100644 src/test-redux.js diff --git a/package.json b/package.json index 3e0e179..beb871a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ }, "dependencies": { "react": "^15.6.1", - "react-dom": "^15.6.1" + "react-dom": "^15.6.1", + "redux": "^3.6.0", + "redux-thunk": "^2.1.0" } } diff --git a/src/actions.js b/src/actions.js new file mode 100644 index 0000000..92b287f --- /dev/null +++ b/src/actions.js @@ -0,0 +1,19 @@ +import C from './constants' + +export const addAppointment = appointment => + ({ + type: C.ADD_APPOINTMENT, + payload: appointment + }) + +export const removeAppointment = id => + ({ + type: C.REMOVE_APPOINTMENT, + payload: id + }) + +export const searchAppointment = (queryText, orderBy, orderDir) => + ({ + type: C.SEARCH_APPOINTMENT, + payload: { queryText, orderBy, orderDir } + }) \ No newline at end of file diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..b6ad2b8 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,8 @@ +const constants = { + FETCH_APPOINTMENTS: "FETCH_APPOINTMENTS", + ADD_APPOINTMENT: "ADD_APPOINTMENT", + REMOVE_APPOINTMENT: "REMOVE_APPOINTMENT", + SEARCH_APPOINTMENT: "SEARCH_APPOINTMENT" +} + +export default constants \ No newline at end of file diff --git a/src/initialState.json b/src/initialState.json new file mode 100644 index 0000000..a47b446 --- /dev/null +++ b/src/initialState.json @@ -0,0 +1,28 @@ +{ + "allAppointments": [ + { + "petName": "Buffy", + "ownerName": "Hassum Harrod", + "aptDate": "2016-06-20 15:30", + "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" + }, + { + "petName": "Spot", + "ownerName": "Constance Smith", + "aptDate": "2016-06-24 08:30", + "aptNotes": "This German Shepherd is having some back pain" + }, + { + "petName": "Goldie", + "ownerName": "Barot Bellingham", + "aptDate": "2016-06-22 15:50", + "aptNotes": "This Goldfish has some weird spots in the belly" + }, + { + "petName": "Mitten", + "ownerName": "Hillary Goldwyn", + "aptDate": "2016-06-21 9:15", + "aptNotes": "Cat has excessive hairballs" + } + ] +} \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..0104eda --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,36 @@ +import C from '../constants' +import appReducer from './reducers' +import thunk from 'redux-thunk' +import { createStore, applyMiddleware } from 'redux' + +const consoleMessages = store => next => action => { + + let result + + console.groupCollapsed(`dispatching action => ${action.type}`) + console.log(`total appointments before action "${action.type}" : `, store.getState().allAppointments.length) + result = next(action) + + let { allAppointments } = store.getState() + + allAppointments.forEach( (appointment) => { + console.log(` + id: ${appointment.id} + petName: ${appointment.petName} + ownerName: ${appointment.ownerName} + aptDate: ${appointment.aptDate} + aptNotes: ${appointment.aptNotes} + `) + }) + + console.log(`total appointments after action "${action.type}" : ${allAppointments.length}`) + + console.groupEnd() + + return result + +} + +export default (initialState={}) => { + return applyMiddleware(thunk,consoleMessages)(createStore)(appReducer, initialState) +} \ No newline at end of file diff --git a/src/store/reducers.js b/src/store/reducers.js new file mode 100644 index 0000000..47b44c0 --- /dev/null +++ b/src/store/reducers.js @@ -0,0 +1,42 @@ +import C from '../constants' +import { combineReducers } from 'redux' +import _ from 'lodash' + +export const allAppointments = (state=[], action) => { + + switch(action.type) { + + case C.ADD_APPOINTMENT : + + return [...state, action.payload] + + case C.REMOVE_APPOINTMENT : + + return state.filter(appointment => appointment.id !== action.payload) + + case C.SEARCH_APPOINTMENT : + + let filteredApts = []; + + state.forEach( appointment => { + if ( + (appointment.petName.toLowerCase().indexOf(action.payload.queryText)!=-1) || + (appointment.ownerName.toLowerCase().indexOf(action.payload.queryText)!=-1) || + (appointment.aptDate.toLowerCase().indexOf(action.payload.queryText)!=-1) || + (appointment.aptNotes.toLowerCase().indexOf(action.payload.queryText)!=-1) + ) { filteredApts.push(appointment); } + } ); + + filteredApts = _.orderBy(filteredApts, appointment => appointment[action.payload.orderBy].toLowerCase(), action.payload.orderDir); + + return filteredApts; + + default: + return state + } + +} + +export default combineReducers({ + allAppointments +}) \ No newline at end of file diff --git a/src/test-redux.js b/src/test-redux.js new file mode 100644 index 0000000..1e96028 --- /dev/null +++ b/src/test-redux.js @@ -0,0 +1,42 @@ +import storeFactory from './store' +import { addAppointment, removeAppointment, searchAppointment } from './actions' + +const store = storeFactory() + +store.dispatch( + addAppointment({ + "id": "1", + "petName": "Buffy", + "ownerName": "Hassum Harrod", + "aptDate": "2016-06-20 15:30", + "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" + }) +) + +store.dispatch( + addAppointment({ + "id": "2", + "petName": "Spot", + "ownerName": "Constance Smith", + "aptDate": "2016-06-24 08:30", + "aptNotes": "This German Shepherd is having some back pain" + }) +) + +store.dispatch( + addAppointment({ + "id": "3", + "petName": "Goldie", + "ownerName": "Barot Smith", + "aptDate": "2016-06-22 15:50", + "aptNotes": "This Goldfish has some weird spots in the belly" + }) +) + +store.dispatch( + searchAppointment("smith", "ownerName", "desc") +) + +store.dispatch( + removeAppointment("2") +) \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index e7f9347..8822c0c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,8 @@ const webpack = require('webpack'); module.exports = { - entry: path.resolve(__dirname, './src/js'), + // entry: path.resolve(__dirname, './src/js'), + entry: "./src/test-redux.js", output: { path: path.resolve(__dirname, './build/assets'), From c203fde3540157059ef089df0b7acf00a75357dc Mon Sep 17 00:00:00 2001 From: geobas Date: Thu, 17 Aug 2017 23:27:14 +0300 Subject: [PATCH 21/24] Add new action creators --- src/actions.js | 42 ++++++++++++++++++++++++++++++++++++++++++ src/constants.js | 9 ++++++++- src/initialState.json | 10 ++++++++-- src/store/index.js | 12 ++++++++---- src/store/reducers.js | 18 ++++++++++++++++-- src/test-redux.js | 11 ++++++++++- 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/src/actions.js b/src/actions.js index 92b287f..6ab445c 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,5 +1,29 @@ import C from './constants' +export const toggleAddForm = (flag=false) => + ({ + type: C.TOGGLE_ADD_FORM, + payload: flag + }) + +export const addError = error => + ({ + type: C.ADD_ERROR, + payload: error + }) + +export const removeError = error => + ({ + type: C.REMOVE_ERROR, + payload: error + }) + +export const toggleErrorMessages = flag => + ({ + type: C.TOGGLE_ERROR_MESSAGES, + payload: flag + }) + export const addAppointment = appointment => ({ type: C.ADD_APPOINTMENT, @@ -16,4 +40,22 @@ export const searchAppointment = (queryText, orderBy, orderDir) => ({ type: C.SEARCH_APPOINTMENT, payload: { queryText, orderBy, orderDir } + }) + +export const setOrderBy = order => + ({ + type: C.SET_ORDER_BY, + payload: order + }) + +export const setOrderDir = order => + ({ + type: C.SET_ORDER_DIR, + payload: order + }) + +export const setQueryText = queryText => + ({ + type: C.SET_QUERY_TEXT, + payload: queryText }) \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index b6ad2b8..6c7b32f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,8 +1,15 @@ const constants = { + TOGGLE_ADD_FORM: "TOGGLE_ADD_FORM", + ADD_ERROR: "ADD_ERROR", + REMOVE_ERROR: "REMOVE_ERROR", + TOGGLE_ERROR_MESSAGES: "TOGGLE_ERROR_MESSAGES", FETCH_APPOINTMENTS: "FETCH_APPOINTMENTS", ADD_APPOINTMENT: "ADD_APPOINTMENT", REMOVE_APPOINTMENT: "REMOVE_APPOINTMENT", - SEARCH_APPOINTMENT: "SEARCH_APPOINTMENT" + SEARCH_APPOINTMENT: "SEARCH_APPOINTMENT", + SET_ORDER_BY: "SET_ORDER_BY", + SET_ORDER_DIR: "SET_ORDER_DIR", + SET_QUERY_TEXT: "SET_QUERY_TEXT" } export default constants \ No newline at end of file diff --git a/src/initialState.json b/src/initialState.json index a47b446..b2fd71d 100644 --- a/src/initialState.json +++ b/src/initialState.json @@ -1,5 +1,8 @@ { - "allAppointments": [ + "aptBodyVisible": "false", + "errorMsg": [], + "errorMsgVisible": "false", + "myAppointments": [ { "petName": "Buffy", "ownerName": "Hassum Harrod", @@ -24,5 +27,8 @@ "aptDate": "2016-06-21 9:15", "aptNotes": "Cat has excessive hairballs" } - ] + ], + "orderBy":"petName", + "orderDir":"asc", + "queryText":"" } \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index 0104eda..2446198 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -8,12 +8,16 @@ const consoleMessages = store => next => action => { let result console.groupCollapsed(`dispatching action => ${action.type}`) - console.log(`total appointments before action "${action.type}" : `, store.getState().allAppointments.length) + console.log(`total appointments before action "${action.type}" : `, store.getState().myAppointments.length) result = next(action) - let { allAppointments } = store.getState() + let { myAppointments } = store.getState() - allAppointments.forEach( (appointment) => { + console.log(` + aptBodyVisible: ${store.getState().aptBodyVisible} + `) + + myAppointments.forEach( (appointment) => { console.log(` id: ${appointment.id} petName: ${appointment.petName} @@ -23,7 +27,7 @@ const consoleMessages = store => next => action => { `) }) - console.log(`total appointments after action "${action.type}" : ${allAppointments.length}`) + console.log(`total appointments after action "${action.type}" : ${myAppointments.length}`) console.groupEnd() diff --git a/src/store/reducers.js b/src/store/reducers.js index 47b44c0..490cadc 100644 --- a/src/store/reducers.js +++ b/src/store/reducers.js @@ -2,7 +2,20 @@ import C from '../constants' import { combineReducers } from 'redux' import _ from 'lodash' -export const allAppointments = (state=[], action) => { +export const aptBodyVisible = (state=[], action) => { + + switch(action.type) { + + case C.TOGGLE_ADD_FORM : + + return action.payload; + + default: + return state + } +} + +export const myAppointments = (state=[], action) => { switch(action.type) { @@ -38,5 +51,6 @@ export const allAppointments = (state=[], action) => { } export default combineReducers({ - allAppointments + aptBodyVisible, + myAppointments }) \ No newline at end of file diff --git a/src/test-redux.js b/src/test-redux.js index 1e96028..49264e8 100644 --- a/src/test-redux.js +++ b/src/test-redux.js @@ -1,8 +1,12 @@ import storeFactory from './store' -import { addAppointment, removeAppointment, searchAppointment } from './actions' +import { toggleAddForm, addAppointment, removeAppointment, searchAppointment } from './actions' const store = storeFactory() +store.dispatch( + toggleAddForm(false) +) + store.dispatch( addAppointment({ "id": "1", @@ -37,6 +41,11 @@ store.dispatch( searchAppointment("smith", "ownerName", "desc") ) + +store.dispatch( + toggleAddForm(true) +) + store.dispatch( removeAppointment("2") ) \ No newline at end of file From 1746ef38c9581022ff63cd2f1d55504975f9b131 Mon Sep 17 00:00:00 2001 From: geobas Date: Sat, 19 Aug 2017 17:13:18 +0300 Subject: [PATCH 22/24] Add more reducers --- src/actions.js | 23 +++++++----- src/store/index.js | 39 +++++++++++++++---- src/store/reducers.js | 87 +++++++++++++++++++++++++++++++++++++++---- src/test-redux.js | 31 +++++++++++---- 4 files changed, 148 insertions(+), 32 deletions(-) diff --git a/src/actions.js b/src/actions.js index 6ab445c..1216745 100644 --- a/src/actions.js +++ b/src/actions.js @@ -12,13 +12,13 @@ export const addError = error => payload: error }) -export const removeError = error => - ({ - type: C.REMOVE_ERROR, - payload: error - }) +// export const removeError = error => +// ({ +// type: C.REMOVE_ERROR, +// payload: error +// }) -export const toggleErrorMessages = flag => +export const toggleErrorMessages = (flag=false) => ({ type: C.TOGGLE_ERROR_MESSAGES, payload: flag @@ -42,19 +42,24 @@ export const searchAppointment = (queryText, orderBy, orderDir) => payload: { queryText, orderBy, orderDir } }) -export const setOrderBy = order => +// export const searchAppointment = () => +// ({ +// type: C.SEARCH_APPOINTMENT, +// }) + +export const setOrderBy = (order='petName') => ({ type: C.SET_ORDER_BY, payload: order }) -export const setOrderDir = order => +export const setOrderDir = (order='asc') => ({ type: C.SET_ORDER_DIR, payload: order }) -export const setQueryText = queryText => +export const setQueryText = (queryText='') => ({ type: C.SET_QUERY_TEXT, payload: queryText diff --git a/src/store/index.js b/src/store/index.js index 2446198..1b62901 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -8,26 +8,49 @@ const consoleMessages = store => next => action => { let result console.groupCollapsed(`dispatching action => ${action.type}`) - console.log(`total appointments before action "${action.type}" : `, store.getState().myAppointments.length) + // console.log(`total appointments before action "${action.type}" : `, store.getState().myAppointments.length) result = next(action) let { myAppointments } = store.getState() + let { errorMsg } = store.getState() console.log(` aptBodyVisible: ${store.getState().aptBodyVisible} `) + + console.log(` + errorMsg count: ${store.getState().errorMsg.length} + `) - myAppointments.forEach( (appointment) => { + errorMsg.forEach( (error) => { console.log(` - id: ${appointment.id} - petName: ${appointment.petName} - ownerName: ${appointment.ownerName} - aptDate: ${appointment.aptDate} - aptNotes: ${appointment.aptNotes} + ${Object.keys(error).map(k => error[k])} `) }) - console.log(`total appointments after action "${action.type}" : ${myAppointments.length}`) + console.log(` + errorMsgVisible: ${store.getState().errorMsgVisible} + `) + + console.table(myAppointments) + + console.log(` + orderBy: ${store.getState().orderBy} + orderDir: ${store.getState().orderDir} + queryText: ${store.getState().queryText} + `) + + // myAppointments.forEach( (appointment) => { + // console.log(` + // id: ${appointment.id} + // petName: ${appointment.petName} + // ownerName: ${appointment.ownerName} + // aptDate: ${appointment.aptDate} + // aptNotes: ${appointment.aptNotes} + // `) + // }) + + // console.log(`total appointments after action "${action.type}" : ${myAppointments.length}`) console.groupEnd() diff --git a/src/store/reducers.js b/src/store/reducers.js index 490cadc..2642aff 100644 --- a/src/store/reducers.js +++ b/src/store/reducers.js @@ -15,6 +15,36 @@ export const aptBodyVisible = (state=[], action) => { } } +export const errorMsg = (state=[], action) => { + + switch(action.type) { + + case C.ADD_ERROR : + + return [...state, action.payload] + + // case C.REMOVE_ERROR : + + // return action.payload; + + default: + return state + } +} + +export const errorMsgVisible = (state=[], action) => { + + switch(action.type) { + + case C.TOGGLE_ERROR_MESSAGES : + + return action.payload; + + default: + return state + } +} + export const myAppointments = (state=[], action) => { switch(action.type) { @@ -33,24 +63,67 @@ export const myAppointments = (state=[], action) => { state.forEach( appointment => { if ( - (appointment.petName.toLowerCase().indexOf(action.payload.queryText)!=-1) || - (appointment.ownerName.toLowerCase().indexOf(action.payload.queryText)!=-1) || - (appointment.aptDate.toLowerCase().indexOf(action.payload.queryText)!=-1) || - (appointment.aptNotes.toLowerCase().indexOf(action.payload.queryText)!=-1) + (appointment.petName.toLowerCase().indexOf(queryText(null, { type: C.SET_QUERY_TEXT, payload: action.payload.queryText }))!=-1) || + (appointment.ownerName.toLowerCase().indexOf(queryText(null, { type: C.SET_QUERY_TEXT, payload: action.payload.queryText }))!=-1) || + (appointment.aptDate.toLowerCase().indexOf(queryText(null, { type: C.SET_QUERY_TEXT, payload: action.payload.queryText }))!=-1) || + (appointment.aptNotes.toLowerCase().indexOf(queryText(null, { type: C.SET_QUERY_TEXT, payload: action.payload.queryText }))!=-1) ) { filteredApts.push(appointment); } } ); - filteredApts = _.orderBy(filteredApts, appointment => appointment[action.payload.orderBy].toLowerCase(), action.payload.orderDir); - + filteredApts = _.orderBy(filteredApts, appointment => appointment[orderBy(null, { type: C.SET_ORDER_BY, payload: action.payload.orderBy })].toLowerCase(), + orderDir(null, { type: C.SET_ORDER_DIR, payload: action.payload.orderDir })); return filteredApts; default: return state } +} + +export const orderBy = (state=[], action) => { + + switch(action.type) { + + case C.SET_ORDER_BY : + + return action.payload; + + default: + return state + } +} + +export const orderDir = (state=[], action) => { + + switch(action.type) { + + case C.SET_ORDER_DIR : + + return action.payload; + + default: + return state + } +} + +export const queryText = (state=[], action) => { + + switch(action.type) { + + case C.SET_QUERY_TEXT : + + return action.payload; + default: + return state + } } export default combineReducers({ aptBodyVisible, - myAppointments + errorMsg, + errorMsgVisible, + myAppointments, + orderBy, + orderDir, + queryText }) \ No newline at end of file diff --git a/src/test-redux.js b/src/test-redux.js index 49264e8..f21bcc4 100644 --- a/src/test-redux.js +++ b/src/test-redux.js @@ -1,10 +1,26 @@ import storeFactory from './store' -import { toggleAddForm, addAppointment, removeAppointment, searchAppointment } from './actions' +import { toggleAddForm, addError, toggleErrorMessages, addAppointment, removeAppointment, searchAppointment, setOrderBy, setOrderDir, setQueryText } from './actions' const store = storeFactory() store.dispatch( - toggleAddForm(false) + toggleAddForm() +) + +store.dispatch( + toggleErrorMessages() +) + +store.dispatch( + addError({ petName : 'Pet name is too short...' }) +) + +store.dispatch( + addError({ inputAptDate : 'Date was not given...' }) +) + +store.dispatch( + toggleErrorMessages(true) ) store.dispatch( @@ -27,6 +43,10 @@ store.dispatch( }) ) +store.dispatch( + setOrderBy("ownerName") +) + store.dispatch( addAppointment({ "id": "3", @@ -38,12 +58,7 @@ store.dispatch( ) store.dispatch( - searchAppointment("smith", "ownerName", "desc") -) - - -store.dispatch( - toggleAddForm(true) + searchAppointment("constance", "petName", "desc") ) store.dispatch( From b5ea2fdb9205d67ff124c03c0c51f296d6bfe5de Mon Sep 17 00:00:00 2001 From: geobas Date: Sat, 19 Aug 2017 17:29:26 +0300 Subject: [PATCH 23/24] Connect MainInterface component to redux --- build/assets/data.json | 54 ++++++++++++----------- package.json | 4 +- src/actions.js | 30 ++++++++++--- src/constants.js | 4 +- src/js/AddAppointment.js | 8 ++-- src/js/ErrorMessage.js | 4 +- src/js/MainInterface.js | 83 ++++++++++++++++++++++-------------- src/js/index.js | 17 ++++++-- src/js/initialState.json | 9 ++++ src/store/reducers.js | 15 ++++--- src/test-redux.js | 92 +++++++++++++++++++++------------------- webpack.config.js | 6 +-- 12 files changed, 200 insertions(+), 126 deletions(-) create mode 100644 src/js/initialState.json diff --git a/build/assets/data.json b/build/assets/data.json index e7d435c..9a561cf 100644 --- a/build/assets/data.json +++ b/build/assets/data.json @@ -1,26 +1,30 @@ [ - { - "petName": "Buffy", - "ownerName": "Hassum Harrod", - "aptDate": "2016-06-20 15:30", - "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" - }, - { - "petName": "Spot", - "ownerName": "Constance Smith", - "aptDate": "2016-06-24 08:30", - "aptNotes": "This German Shepherd is having some back pain" - }, - { - "petName": "Goldie", - "ownerName": "Barot Bellingham", - "aptDate": "2016-06-22 15:50", - "aptNotes": "This Goldfish has some weird spots in the belly" - }, - { - "petName": "Mitten", - "ownerName": "Hillary Goldwyn", - "aptDate": "2016-06-21 9:15", - "aptNotes": "Cat has excessive hairballs" - } -] + { + "id": "1", + "petName": "Buffy", + "ownerName": "Hassum Harrod", + "aptDate": "2016-06-20 15:30", + "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" + }, + { + "id": "2", + "petName": "Spot", + "ownerName": "Constance Smith", + "aptDate": "2016-06-24 08:30", + "aptNotes": "This German Shepherd is having some back pain" + }, + { + "id": "3", + "petName": "Goldie", + "ownerName": "Barot Bellingham", + "aptDate": "2016-06-22 15:50", + "aptNotes": "This Goldfish has some weird spots in the belly" + }, + { + "id": "4", + "petName": "Mitten", + "ownerName": "Hillary Goldwyn", + "aptDate": "2016-06-21 9:15", + "aptNotes": "Cat has excessive hairballs" + } +] \ No newline at end of file diff --git a/package.json b/package.json index beb871a..f3d007e 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "react": "^15.6.1", "react-dom": "^15.6.1", "redux": "^3.6.0", - "redux-thunk": "^2.1.0" + "redux-thunk": "^2.1.0", + "react-redux": "^5.0.4", + "isomorphic-fetch": "^2.2.1" } } diff --git a/src/actions.js b/src/actions.js index 1216745..4264d89 100644 --- a/src/actions.js +++ b/src/actions.js @@ -12,11 +12,10 @@ export const addError = error => payload: error }) -// export const removeError = error => -// ({ -// type: C.REMOVE_ERROR, -// payload: error -// }) +export const clearErrors = () => + ({ + type: C.CLEAR_ERRORS + }) export const toggleErrorMessages = (flag=false) => ({ @@ -24,6 +23,27 @@ export const toggleErrorMessages = (flag=false) => payload: flag }) +export const fetchAppointmentsSuccess = appointments => + ({ + type: C.FETCH_APPOINTMENTS_SUCCESS, + payload: appointments + }) + +export const fetchAppointments = url => { + return (dispatch) => { + fetch(url) + .then((response) => { + if (!response.ok) { + throw Error(`${response.status} URL ${response.statusText}`); + } + + return response; + }) + .then((response) => response.json()) + .then((appointments) => dispatch(fetchAppointmentsSuccess(appointments))) + } +} + export const addAppointment = appointment => ({ type: C.ADD_APPOINTMENT, diff --git a/src/constants.js b/src/constants.js index 6c7b32f..11665b8 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,9 +1,9 @@ const constants = { TOGGLE_ADD_FORM: "TOGGLE_ADD_FORM", ADD_ERROR: "ADD_ERROR", - REMOVE_ERROR: "REMOVE_ERROR", + CLEAR_ERRORS: "CLEAR_ERRORS", TOGGLE_ERROR_MESSAGES: "TOGGLE_ERROR_MESSAGES", - FETCH_APPOINTMENTS: "FETCH_APPOINTMENTS", + FETCH_APPOINTMENTS_SUCCESS: "FETCH_APPOINTMENTS_SUCCESS", ADD_APPOINTMENT: "ADD_APPOINTMENT", REMOVE_APPOINTMENT: "REMOVE_APPOINTMENT", SEARCH_APPOINTMENT: "SEARCH_APPOINTMENT", diff --git a/src/js/AddAppointment.js b/src/js/AddAppointment.js index da5221b..efb73c3 100644 --- a/src/js/AddAppointment.js +++ b/src/js/AddAppointment.js @@ -26,16 +26,16 @@ export default class AddAppointment extends Component { let errorMsg = []; const petName = this.refs.inputPetName.value; - if ( petName.length < 5 ) errorMsg.push({ petName : 'Pet name is too short...' }); + if ( petName.length < 5 ) errorMsg.push('Pet name is too short...'); const ownerName = this.refs.inputOwnerName.value; - if ( ownerName.length < 5 ) errorMsg.push({ ownerName : 'Owner name is too short...' }); + if ( ownerName.length < 5 ) errorMsg.push('Owner name is too short...'); const aptDate = this.refs.inputAptDate.value; - if ( !aptDate ) errorMsg.push({ inputAptDate : 'Date was not given...' }); + if ( !aptDate ) errorMsg.push('Date was not given...'); const aptTime = this.refs.inputAptTime.value; - if ( !aptTime ) errorMsg.push({ aptTime : 'Time was not given...' }); + if ( !aptTime ) errorMsg.push('Time was not given...'); if ( errorMsg.length > 0) { this.props.addErrorMsg(errorMsg, true); diff --git a/src/js/ErrorMessage.js b/src/js/ErrorMessage.js index c935102..d2a7e2c 100644 --- a/src/js/ErrorMessage.js +++ b/src/js/ErrorMessage.js @@ -17,8 +17,8 @@ export default class ErrorMessage extends Component { this.displayErrors = { display }; } - showErrors(error, i) { - return

    { Object.keys(error).map(k => error[k]) }

    ; + showErrors(errors, i) { + return errors.map( (error,i) =>

    { error }

    ) } render() { diff --git a/src/js/MainInterface.js b/src/js/MainInterface.js index 246d362..050e21a 100644 --- a/src/js/MainInterface.js +++ b/src/js/MainInterface.js @@ -7,18 +7,14 @@ import ErrorMessage from './ErrorMessage'; import AddAppointment from './AddAppointment'; import SearchAppointments from './SearchAppointments'; -export default class MainInterface extends Component { +import { connect } from 'react-redux'; +import { fetchAppointments, toggleAddForm, addAppointment, addError, + toggleErrorMessages, clearErrors, setOrderBy, setOrderDir, + setQueryText, removeAppointment } from '../actions'; + +class MainInterface extends Component { constructor(props) { super(props); - this.state = { - aptBodyVisible: false, - orderBy: 'petName', - orderDir: 'asc', - queryText: '', - myAppointments: [], - errorMsg: [], - errorMsgVisible: false - }; this.deleteMessage = this.deleteMessage.bind(this); this.toggleAddDisplay = this.toggleAddDisplay.bind(this); this.addItem = this.addItem.bind(this); @@ -29,7 +25,7 @@ export default class MainInterface extends Component { } componentDidMount() { - this.serverRequest = $.get('./assets/data.json', result => this.setState( { myAppointments: result } )); + this.serverRequest = this.props.fetchAppointments('./assets/data.json'); } componentWillUnmount() { @@ -37,37 +33,35 @@ export default class MainInterface extends Component { } deleteMessage(item) { - let newApts = _.without(this.state.myAppointments, item); - this.setState({ myAppointments: newApts }); + this.props.removeAppointment(item.id); } toggleAddDisplay() { - let tempVisibility = !this.state.aptBodyVisible; - this.setState({ aptBodyVisible: tempVisibility }); + this.props.toggleAddDisplay(!this.props.aptBodyVisible); } addItem(tempItem) { - let tempApts = this.state.myAppointments; - tempApts.push(tempItem); - this.setState({ myAppointments: tempApts }); + this.props.addItem(tempItem); } addError(errorItems, visible) { - this.setState({ errorMsg: errorItems }); - this.setState({ errorMsgVisible: visible }); + this.props.clearErrors(); + this.props.addError(errorItems); + this.props.toggleErrorMessages(visible); } reOrder(orderBy, orderDir) { - this.setState({ orderBy, orderDir }); + this.props.setOrderBy(orderBy); + this.props.setOrderDir(orderDir); } setQueryText(q) { - this.setState({ queryText: q }); + this.props.setQueryText(q); } showAppointments() { - let myAppointments = this.state.myAppointments; - let queryText = this.state.queryText; + let myAppointments = this.props.myAppointments; + let queryText = this.props.queryText; let filteredApts = []; myAppointments.forEach( item => { @@ -79,7 +73,7 @@ export default class MainInterface extends Component { ) { filteredApts.push(item); } } ); - filteredApts = _.orderBy(filteredApts, item => item[this.state.orderBy].toLowerCase(), this.state.orderDir); + filteredApts = _.orderBy(filteredApts, item => item[this.props.orderBy].toLowerCase(), this.props.orderDir); filteredApts = filteredApts.map( (item, index) => { return ( @@ -99,18 +93,18 @@ export default class MainInterface extends Component { return (
    @@ -119,4 +113,31 @@ export default class MainInterface extends Component { ) } -} \ No newline at end of file +} + +const mapStateToProps = (state) => + ({ + aptBodyVisible: state.aptBodyVisible, + errorMsg: state.errorMsg, + errorMsgVisible: state.errorMsgVisible, + myAppointments: state.myAppointments, + orderBy: state.orderBy, + orderDir: state.orderDir, + queryText: state.queryText + }) + +const mapDispatchToProps = (dispatch) => + ({ + fetchAppointments: (url) => dispatch(fetchAppointments(url)), + toggleAddDisplay: (flag) => dispatch(toggleAddForm(flag)), + addItem: (item) => dispatch(addAppointment(item)), + addError: (errorItems) => dispatch(addError(errorItems)), + toggleErrorMessages: (visible) => dispatch(toggleErrorMessages(visible)), + clearErrors: () => dispatch(clearErrors()), + setOrderBy: (orderBy) => dispatch(setOrderBy(orderBy)), + setOrderDir: (orderDir) => dispatch(setOrderDir(orderDir)), + setQueryText: (qt) => dispatch(setQueryText(qt)), + removeAppointment: (id) => dispatch(removeAppointment(id)) + }) + +export default connect(mapStateToProps, mapDispatchToProps)(MainInterface); \ No newline at end of file diff --git a/src/js/index.js b/src/js/index.js index e16f4dd..1608db7 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,7 +1,16 @@ +import C from '../constants' import React from 'react'; -import ReactDOM from 'react-dom'; +import { render } from 'react-dom' import MainInterface from './MainInterface'; +import sampleData from './initialState' +import storeFactory from '../store' +import { Provider } from 'react-redux' -ReactDOM.render( - , document.getElementById('petAppointments') -); \ No newline at end of file +const store = storeFactory(sampleData) + +render( + + + , + document.getElementById('petAppointments') +) \ No newline at end of file diff --git a/src/js/initialState.json b/src/js/initialState.json new file mode 100644 index 0000000..58a571d --- /dev/null +++ b/src/js/initialState.json @@ -0,0 +1,9 @@ +{ + "aptBodyVisible": false, + "errorMsg": [], + "errorMsgVisible": false, + "myAppointments": [], + "orderBy": "petName", + "orderDir": "asc", + "queryText": "" +} \ No newline at end of file diff --git a/src/store/reducers.js b/src/store/reducers.js index 2642aff..1f46b7f 100644 --- a/src/store/reducers.js +++ b/src/store/reducers.js @@ -23,9 +23,9 @@ export const errorMsg = (state=[], action) => { return [...state, action.payload] - // case C.REMOVE_ERROR : + case C.CLEAR_ERRORS : - // return action.payload; + return [] default: return state @@ -49,6 +49,10 @@ export const myAppointments = (state=[], action) => { switch(action.type) { + case C.FETCH_APPOINTMENTS_SUCCESS : + + return action.payload + case C.ADD_APPOINTMENT : return [...state, action.payload] @@ -70,8 +74,9 @@ export const myAppointments = (state=[], action) => { ) { filteredApts.push(appointment); } } ); - filteredApts = _.orderBy(filteredApts, appointment => appointment[orderBy(null, { type: C.SET_ORDER_BY, payload: action.payload.orderBy })].toLowerCase(), - orderDir(null, { type: C.SET_ORDER_DIR, payload: action.payload.orderDir })); + filteredApts = _.orderBy(filteredApts, appointment => + appointment[orderBy(null, { type: C.SET_ORDER_BY, payload: action.payload.orderBy })].toLowerCase(), + orderDir(null, { type: C.SET_ORDER_DIR, payload: action.payload.orderDir })); return filteredApts; default: @@ -90,7 +95,7 @@ export const orderBy = (state=[], action) => { default: return state } -} +} export const orderDir = (state=[], action) => { diff --git a/src/test-redux.js b/src/test-redux.js index f21bcc4..f87d9ef 100644 --- a/src/test-redux.js +++ b/src/test-redux.js @@ -1,61 +1,65 @@ import storeFactory from './store' -import { toggleAddForm, addError, toggleErrorMessages, addAppointment, removeAppointment, searchAppointment, setOrderBy, setOrderDir, setQueryText } from './actions' +import { toggleAddForm, addError, toggleErrorMessages, fetchAppointments, addAppointment, removeAppointment, searchAppointment, setOrderBy, setOrderDir, setQueryText } from './actions' const store = storeFactory() store.dispatch( - toggleAddForm() + fetchAppointments('./assets/data.json') ) -store.dispatch( - toggleErrorMessages() -) +// store.dispatch( +// toggleAddForm() +// ) -store.dispatch( - addError({ petName : 'Pet name is too short...' }) -) +// store.dispatch( +// toggleErrorMessages() +// ) -store.dispatch( - addError({ inputAptDate : 'Date was not given...' }) -) +// store.dispatch( +// addError({ petName : 'Pet name is too short...' }) +// ) -store.dispatch( - toggleErrorMessages(true) -) +// store.dispatch( +// addError({ inputAptDate : 'Date was not given...' }) +// ) -store.dispatch( - addAppointment({ - "id": "1", - "petName": "Buffy", - "ownerName": "Hassum Harrod", - "aptDate": "2016-06-20 15:30", - "aptNotes": "This Chihuahua has not eaten for three days and is lethargic" - }) -) +// store.dispatch( +// toggleErrorMessages(true) +// ) -store.dispatch( - addAppointment({ - "id": "2", - "petName": "Spot", - "ownerName": "Constance Smith", - "aptDate": "2016-06-24 08:30", - "aptNotes": "This German Shepherd is having some back pain" - }) -) +// store.dispatch( +// addAppointment({ +// "id": "5", +// "petName": "testakis", +// "ownerName": "geo bas", +// "aptDate": "2016-06-20 15:30", +// "aptNotes": "xxxx" +// }) +// ) -store.dispatch( - setOrderBy("ownerName") -) +// store.dispatch( +// addAppointment({ +// "id": "2", +// "petName": "Spot", +// "ownerName": "Constance Smith", +// "aptDate": "2016-06-24 08:30", +// "aptNotes": "This German Shepherd is having some back pain" +// }) +// ) -store.dispatch( - addAppointment({ - "id": "3", - "petName": "Goldie", - "ownerName": "Barot Smith", - "aptDate": "2016-06-22 15:50", - "aptNotes": "This Goldfish has some weird spots in the belly" - }) -) +// store.dispatch( +// setOrderBy("ownerName") +// ) + +// store.dispatch( +// addAppointment({ +// "id": "3", +// "petName": "Goldie", +// "ownerName": "Barot Smith", +// "aptDate": "2016-06-22 15:50", +// "aptNotes": "This Goldfish has some weird spots in the belly" +// }) +// ) store.dispatch( searchAppointment("constance", "petName", "desc") diff --git a/webpack.config.js b/webpack.config.js index 8822c0c..2ec3425 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,8 +4,8 @@ const webpack = require('webpack'); module.exports = { - // entry: path.resolve(__dirname, './src/js'), - entry: "./src/test-redux.js", + entry: path.resolve(__dirname, './src/js'), + // entry: "./src/test-redux.js", output: { path: path.resolve(__dirname, './build/assets'), @@ -42,7 +42,7 @@ module.exports = { // test: /\.json$/, // exclude: /(node_modules)/, // loader: 'json-loader' - // }, + // } ] }, From a6de529d6c6aa59b1f303d4e1e27c3691db6ea8e Mon Sep 17 00:00:00 2001 From: geobas Date: Sat, 19 Aug 2017 17:37:08 +0300 Subject: [PATCH 24/24] Update README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) mode change 100755 => 100644 README.md diff --git a/README.md b/README.md old mode 100755 new mode 100644 index ee785a5..68eefab --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Building an Interface with React This is the repository for the course, [Building an Interface with React](https://www.lynda.com/React-js-tutorials/Building-Web-Interface-React-js/495271-2.html). -The code is converted from ES5 to ES6, gulp is replaced with webpack and all React components are tested. +The code is converted from ES5 to ES6, gulp is replaced with webpack and React components are connected to Redux. ## Instructions This repository has branches for each of the videos in the course. You can use the branch pop up menu in github to switch to a specific branch and take a look at the course at that stage. Or you can simply add `/tree/BRANCH_NAME` to the URL to go to the branch you want to peek at. @@ -14,5 +14,4 @@ This repository has branches for each of the videos in the course. You can use t 3. CD to the folder `cd reactinterface` 4. Run `> npm install` to install the project dependencies 5. Run `> npm start` command to start webpack-dev-server -6. Run `> npm test` command to start Karma -7. Build something awesome +6. Build something awesome \ No newline at end of file