From d7c55853e9d180290ae3cb30b2f329ebab8748ce Mon Sep 17 00:00:00 2001
From: Kai Schaper <303@posteo.de>
Date: Mon, 10 Oct 2016 04:01:10 +0200
Subject: [PATCH 1/5] set up Mocha/Chai/Enzyme for React component unit testing

---
 .nvmrc                                             |  1 +
 package.json                                       | 14 ++++++++++----
 .../components/loading_indicator.test.jsx          | 13 +++++++++++++
 3 files changed, 24 insertions(+), 4 deletions(-)
 create mode 100644 .nvmrc
 create mode 100644 spec/javascript/components/loading_indicator.test.jsx

diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..f0e13c509
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+6.7.0
diff --git a/package.json b/package.json
index 1ea514e1f..f5244a2b0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,10 @@
 {
   "name": "mastodon",
+  "scripts": {
+    "test": "mocha --compilers js:babel-register ./spec/javascript/components/*.test.jsx"
+  },
   "devDependencies": {
+    "axios": "^0.14.0",
     "babel-plugin-react-transform": "^2.0.2",
     "babel-plugin-transform-object-rest-spread": "^6.8.0",
     "babel-preset-es2015": "^6.13.2",
@@ -8,17 +12,19 @@
     "babelify": "^7.3.0",
     "browserify": "^13.1.0",
     "browserify-incremental": "^3.1.1",
-    "react": "^15.3.0",
-    "react-dom": "^15.3.0",
-    "react-proxy": "^1.1.8",
-    "axios": "^0.14.0",
+    "chai": "^3.5.0",
+    "enzyme": "^2.4.1",
     "es6-promise": "^3.2.1",
     "immutable": "^3.8.1",
+    "mocha": "^3.1.1",
     "moment": "^2.14.1",
     "react-addons-perf": "^15.3.2",
     "react-addons-pure-render-mixin": "^15.3.1",
+    "react-addons-test-utils": "^15.3.2",
+    "react-dom": "^15.3.0",
     "react-immutable-proptypes": "^2.1.0",
     "react-notification": "^6.1.1",
+    "react-proxy": "^1.1.8",
     "react-redux": "^5.0.0-beta.3",
     "react-redux-loading-bar": "^2.3.3",
     "react-router": "^2.8.0",
diff --git a/spec/javascript/components/loading_indicator.test.jsx b/spec/javascript/components/loading_indicator.test.jsx
new file mode 100644
index 000000000..b2f9c919e
--- /dev/null
+++ b/spec/javascript/components/loading_indicator.test.jsx
@@ -0,0 +1,13 @@
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+global.React = React;
+
+import LoadingIndicator from '../../../app/assets/javascripts/components/components/loading_indicator'
+
+describe('<LoadingIndicator />', function() {
+  it('renders text that indicates loading', function() {
+    const wrapper = shallow(<LoadingIndicator />);
+    expect(wrapper.text()).to.match(/loading/i);
+  });
+});

From 1a1b9bbbc0874bfd8542ccb149589ef7757e4415 Mon Sep 17 00:00:00 2001
From: Kai Schaper <303@posteo.de>
Date: Mon, 10 Oct 2016 20:05:39 +0200
Subject: [PATCH 2/5] add required peer dependency

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index f5244a2b0..204d2bdfe 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "immutable": "^3.8.1",
     "mocha": "^3.1.1",
     "moment": "^2.14.1",
+    "react": "^15.3.2",
     "react-addons-perf": "^15.3.2",
     "react-addons-pure-render-mixin": "^15.3.1",
     "react-addons-test-utils": "^15.3.2",

From 998f161e1d73ee699a99a65fa5e7701ea6602567 Mon Sep 17 00:00:00 2001
From: Kai Schaper <303@posteo.de>
Date: Mon, 10 Oct 2016 22:32:03 +0200
Subject: [PATCH 3/5] add jsdom; add basic Avatar component test

---
 package.json                                  |  3 ++-
 spec/javascript/components/avatar.test.jsx    | 12 ++++++++++
 .../components/loading_indicator.test.jsx     |  2 --
 spec/javascript/setup.js                      | 22 +++++++++++++++++++
 4 files changed, 36 insertions(+), 3 deletions(-)
 create mode 100644 spec/javascript/components/avatar.test.jsx
 create mode 100644 spec/javascript/setup.js

diff --git a/package.json b/package.json
index 204d2bdfe..5d03df018 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "mastodon",
   "scripts": {
-    "test": "mocha --compilers js:babel-register ./spec/javascript/components/*.test.jsx"
+    "test": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx"
   },
   "devDependencies": {
     "axios": "^0.14.0",
@@ -16,6 +16,7 @@
     "enzyme": "^2.4.1",
     "es6-promise": "^3.2.1",
     "immutable": "^3.8.1",
+    "jsdom": "^9.6.0",
     "mocha": "^3.1.1",
     "moment": "^2.14.1",
     "react": "^15.3.2",
diff --git a/spec/javascript/components/avatar.test.jsx b/spec/javascript/components/avatar.test.jsx
new file mode 100644
index 000000000..f69b538a2
--- /dev/null
+++ b/spec/javascript/components/avatar.test.jsx
@@ -0,0 +1,12 @@
+import { expect } from 'chai';
+import { render } from 'enzyme';
+
+import Avatar from '../../../app/assets/javascripts/components/components/avatar'
+
+describe('<Avatar />', function() {
+  it('renders an img with the given src', function() {
+    const src = '/path/to/image.jpg';
+    const wrapper = render(<Avatar src={src} size={100} />);
+    expect(wrapper.find(`img[src="${src}"]`)).to.have.length(1);
+  });
+});
diff --git a/spec/javascript/components/loading_indicator.test.jsx b/spec/javascript/components/loading_indicator.test.jsx
index b2f9c919e..e62288405 100644
--- a/spec/javascript/components/loading_indicator.test.jsx
+++ b/spec/javascript/components/loading_indicator.test.jsx
@@ -1,7 +1,5 @@
 import { expect } from 'chai';
 import { shallow } from 'enzyme';
-import React from 'react';
-global.React = React;
 
 import LoadingIndicator from '../../../app/assets/javascripts/components/components/loading_indicator'
 
diff --git a/spec/javascript/setup.js b/spec/javascript/setup.js
new file mode 100644
index 000000000..636cdcc7e
--- /dev/null
+++ b/spec/javascript/setup.js
@@ -0,0 +1,22 @@
+/**
+ * http://airbnb.io/enzyme/docs/guides/jsdom.html
+ */
+var jsdom = require('jsdom').jsdom;
+
+var exposedProperties = ['window', 'navigator', 'document'];
+
+global.document = jsdom('');
+global.window = document.defaultView;
+Object.keys(document.defaultView).forEach((property) => {
+  if (typeof global[property] === 'undefined') {
+    exposedProperties.push(property);
+    global[property] = document.defaultView[property];
+  }
+});
+
+global.navigator = {
+  userAgent: 'node.js'
+};
+
+var React    = window.React    = global.React    = require('react');
+var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');

From e0a44556221ab2555731315a033fd18a5c01ef19 Mon Sep 17 00:00:00 2001
From: Kai Schaper <303@posteo.de>
Date: Mon, 10 Oct 2016 22:44:06 +0200
Subject: [PATCH 4/5] add sinon; add basic Button component test

---
 package.json                               |  3 ++-
 spec/javascript/components/button.test.jsx | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)
 create mode 100644 spec/javascript/components/button.test.jsx

diff --git a/package.json b/package.json
index 5d03df018..e980a72b2 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
     "redux": "^3.5.2",
     "redux-immutable": "^3.0.8",
     "redux-thunk": "^2.1.0",
-    "reselect": "^2.5.4"
+    "reselect": "^2.5.4",
+    "sinon": "^1.17.6"
   }
 }
diff --git a/spec/javascript/components/button.test.jsx b/spec/javascript/components/button.test.jsx
new file mode 100644
index 000000000..5610e67dd
--- /dev/null
+++ b/spec/javascript/components/button.test.jsx
@@ -0,0 +1,14 @@
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+
+import Button from '../../../app/assets/javascripts/components/components/button'
+
+describe('<Button />', function() {
+  it('simulates click events', function() {
+    const onClick = sinon.spy();
+    const wrapper = shallow(<Button onClick={onClick} />);
+    wrapper.find('button').simulate('click');
+    expect(onClick.calledOnce).to.equal(true);
+  });
+});

From ecd4042c209a402c1ccace7e2b989783dce03436 Mon Sep 17 00:00:00 2001
From: Kai Schaper <303@posteo.de>
Date: Mon, 10 Oct 2016 22:46:37 +0200
Subject: [PATCH 5/5] use ES6 arrow functions

---
 spec/javascript/components/avatar.test.jsx            | 4 ++--
 spec/javascript/components/button.test.jsx            | 4 ++--
 spec/javascript/components/loading_indicator.test.jsx | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/spec/javascript/components/avatar.test.jsx b/spec/javascript/components/avatar.test.jsx
index f69b538a2..79b7d02f4 100644
--- a/spec/javascript/components/avatar.test.jsx
+++ b/spec/javascript/components/avatar.test.jsx
@@ -3,8 +3,8 @@ import { render } from 'enzyme';
 
 import Avatar from '../../../app/assets/javascripts/components/components/avatar'
 
-describe('<Avatar />', function() {
-  it('renders an img with the given src', function() {
+describe('<Avatar />', () => {
+  it('renders an img with the given src', () => {
     const src = '/path/to/image.jpg';
     const wrapper = render(<Avatar src={src} size={100} />);
     expect(wrapper.find(`img[src="${src}"]`)).to.have.length(1);
diff --git a/spec/javascript/components/button.test.jsx b/spec/javascript/components/button.test.jsx
index 5610e67dd..0f16ebe8e 100644
--- a/spec/javascript/components/button.test.jsx
+++ b/spec/javascript/components/button.test.jsx
@@ -4,8 +4,8 @@ import sinon from 'sinon';
 
 import Button from '../../../app/assets/javascripts/components/components/button'
 
-describe('<Button />', function() {
-  it('simulates click events', function() {
+describe('<Button />', () => {
+  it('simulates click events', () => {
     const onClick = sinon.spy();
     const wrapper = shallow(<Button onClick={onClick} />);
     wrapper.find('button').simulate('click');
diff --git a/spec/javascript/components/loading_indicator.test.jsx b/spec/javascript/components/loading_indicator.test.jsx
index e62288405..7039dbfbd 100644
--- a/spec/javascript/components/loading_indicator.test.jsx
+++ b/spec/javascript/components/loading_indicator.test.jsx
@@ -3,8 +3,8 @@ import { shallow } from 'enzyme';
 
 import LoadingIndicator from '../../../app/assets/javascripts/components/components/loading_indicator'
 
-describe('<LoadingIndicator />', function() {
-  it('renders text that indicates loading', function() {
+describe('<LoadingIndicator />', () => {
+  it('renders text that indicates loading', () => {
     const wrapper = shallow(<LoadingIndicator />);
     expect(wrapper.text()).to.match(/loading/i);
   });