Rafał Pitoń 9 лет назад
Родитель
Сommit
1ae1f1006a

+ 1 - 2
frontend/gulpfile.js

@@ -205,8 +205,7 @@ gulp.task('linttests', function() {
 
 gulp.task('test', ['linttests', 'lintsource'], function() {
   var mochify = require('mochify');
-  //mochify('src/test-setup.js tests/**/*.js', {
-  mochify('src/test-setup.js tests/navbar-dropdown.js', {
+  mochify('src/test-setup.js tests/**/*.js', {
       reporter: 'spec'
     })
     .transform(babelify)

+ 0 - 1
frontend/src/components/user-menu/user-nav.js

@@ -74,7 +74,6 @@ export class UserNav extends React.Component {
 }
 
 export function selectUserMenu(state) {
-  console.log(state);
   return {user: state.auth.user};
 }
 

+ 0 - 2
frontend/src/services/mobile-navbar-dropdown.js

@@ -1,9 +1,7 @@
-import ReactDOM from 'react-dom';
 import mount from 'misago/utils/mount-component';
 
 export class MobileNavbarDropdown {
   init(element) {
-    console.log('init(): ' + ReactDOM.__misago);
     this._element = element;
     this._component = null;
   }

+ 2 - 177
frontend/src/test-setup.js

@@ -14,7 +14,8 @@ require('jquery-mockjax')(jQuery, window);
 $.mockjaxSettings.logging = false;
 $.mockjaxSettings.responseTime = 50;
 
-//require("babel-polyfill");
+// polyfill es6 features in phantom.js
+require("babel-polyfill");
 
 // Mock base href element
 $('head').append('<base href="/test-runner/">');
@@ -25,182 +26,6 @@ $('body').append('<div id="dropdown-mount"></div>');
 $('body').append('<div id="page-mount"></div>');
 $('body').append('<div id="test-mount"></div>');
 
-// set global utility function for cleaning test containers
-var ReactDOM = require('react-dom');
-ReactDOM.__misago = 'ONE ReactDOM';
-
-global.emptyTestContainers = function() {
-  var ReactDOM = require('react-dom');
-  console.log(ReactDOM.__misago);
-  ReactDOM.unmountComponentAtNode(document.getElementById('dropdown-mount'));
-  ReactDOM.unmountComponentAtNode(document.getElementById('modal-mount'));
-  ReactDOM.unmountComponentAtNode(document.getElementById('page-mount'));
-  ReactDOM.unmountComponentAtNode(document.getElementById('test-mount'));
-};
-
-// global utility for mocking context
-global.contextClear = function(misago) {
-  misago._context = {};
-};
-
-global.contextGuest = function(misago) {
-  misago._context = Object.assign({}, misago._context, {
-    isAuthenticated: false,
-
-    user: {
-      id : null,
-
-      acl: {}
-    }
-  });
-};
-
-global.contextAuthenticated = function(misago, overrides) {
-  misago._context = Object.assign({}, misago._context, {
-    isAuthenticated: true,
-
-    user: {
-      id : 42,
-      absolute_url: "/user/loremipsum-1/",
-      avatar_hash: "5c6a04b4",
-      email: "test@example.com",
-      full_title: "Forum team",
-      is_hiding_presence: false,
-      joined_on: "2015-05-09T16:13:33.973603Z",
-      limits_private_thread_invites_to: 0,
-      new_notifications: 0,
-      posts: 30,
-      rank: {
-        id: 1,
-
-        css_class: "team",
-        description: '<p>Lorem ipsum dolor met sit amet elit, si vis pacem para bellum.</p>\n<p>To help see <a href="http://wololo.com/something.php?page=2131">http://wololo.com/something.php?page=2131</a></p>',
-        is_tab: true,
-        name: "Forum team",
-        slug: "forum-team",
-        title: "Team"
-      },
-      short_title: "Team",
-      slug: "loremipsum",
-      subscribe_to_replied_threads: 2,
-      subscribe_to_started_threads: 1,
-      threads: 0,
-      title: "",
-      unread_private_threads: 0,
-      username: "LoremIpsum",
-
-      acl: {}
-    }
-  });
-
-  if (overrides) {
-    Object.assign(misago._context.user, overrides);
-  }
-};
-
-// global utility function for store mocking
-global.initEmptyStore = function(store) {
-  store.constructor();
-  store.addReducer('test', function(state={}, action=null) { /*throw new Error("goddamit");*/ return {}; }, {}); // jshint ignore:line
-  store.init();
-};
-
-global.snackbarStoreMock = function() {
-  return {
-    message: null,
-    _callback: null,
-
-    callback: function(callback) {
-      this._callback = callback;
-    },
-
-    dispatch: function(action) {
-      if (action.type === 'SHOW_SNACKBAR') {
-        this.message = {
-          message: action.message,
-          type: action.messageType
-        };
-
-        if (this._callback) {
-          var self = this;
-          window.setTimeout(function() {
-            self._callback(self.message);
-          }, 100);
-        }
-      }
-    }
-  };
-};
-
-// global init function for modal and dropdown services
-global.initModal = function(modal) {
-  $('#modal-mount').off();
-  modal.init(document.getElementById('modal-mount'));
-};
-
-global.initDropdown = function(dropdown) {
-  dropdown.init(document.getElementById('dropdown-mount'));
-};
-
-// global util for reseting snackbar
-global.snackbarClear = function(snackbar) {
-  // NOTE: Never ever cause situation when snackbar is triggered more than once
-  // in the single test, because this results in race condition within tests
-  // suite where one tests check's snackbar state before it has reopened with
-  // new message set by current test
-  if (snackbar._timeout) {
-    window.clearTimeout(snackbar._timeout);
-    snackbar._timeout = null;
-  }
-};
-
-// global util functions for events
-var ReactTestUtils = require('react-addons-test-utils');
-global.simulateClick = function(selector) {
-  if ($(selector).length) {
-    ReactTestUtils.Simulate.click($(selector).get(0));
-  } else {
-    throw 'selector "' + selector + '" did not match anything';
-  }
-};
-
-global.simulateSubmit = function(selector) {
-  if ($(selector).length) {
-    ReactTestUtils.Simulate.submit($(selector).get(0));
-  } else {
-    throw 'selector "' + selector + '" did not match anything';
-  }
-};
-
-global.simulateChange = function(selector, value) {
-  if ($(selector).length) {
-    $(selector).val(value);
-    ReactTestUtils.Simulate.change($(selector).get(0));
-  } else {
-    throw 'selector "' + selector + '" did not match anything';
-  }
-};
-
-global.afterAjax = function(callback) {
-  window.setTimeout(function() {
-    callback();
-  }, 200);
-};
-
-global.onElement = function(selector, callback) {
-  var _getElement = function() {
-    window.setTimeout(function() {
-      var element = $(selector);
-      if (element.length >= 1) {
-        callback(element);
-      } else {
-        _getElement();
-      }
-    }, 50);
-  };
-
-  _getElement();
-};
 
 // inlined gettext functions form Django
 // jshint ignore: start

+ 0 - 1
frontend/src/utils/mount-component.js

@@ -5,7 +5,6 @@ import store from 'misago/services/store'; // jshint ignore:line
 
 export default function(Component, rootElementId, connected=true) {
   let rootElement = document.getElementById(rootElementId);
-  console.log('mount(): ' + ReactDOM.__misago);
 
   if (rootElement) {
     if (connected) {

+ 177 - 0
frontend/src/utils/test-utils.js

@@ -0,0 +1,177 @@
+import React from 'react'; // jshint ignore:line
+import ReactDOM from 'react-dom';
+import ReactTestUtils from 'react-addons-test-utils';
+
+// clean test mounts from components
+export function render(Component, containerId) {
+  return ReactDOM.render(Component, document.getElementById(containerId));
+}
+
+export function emptyTestContainers() {
+  ReactDOM.unmountComponentAtNode(document.getElementById('dropdown-mount'));
+  ReactDOM.unmountComponentAtNode(document.getElementById('modal-mount'));
+  ReactDOM.unmountComponentAtNode(document.getElementById('page-mount'));
+  ReactDOM.unmountComponentAtNode(document.getElementById('test-mount'));
+}
+
+// global utility for mocking context
+export function contextClear(misago) {
+  misago._context = {};
+}
+
+export function contextGuest(misago) {
+  misago._context = Object.assign({}, misago._context, {
+    isAuthenticated: false,
+
+    user: {
+      id : null,
+
+      acl: {}
+    }
+  });
+}
+
+export function contextAuthenticated(misago, overrides) {
+  misago._context = Object.assign({}, misago._context, {
+    isAuthenticated: true,
+
+    user: {
+      id : 42,
+      absolute_url: "/user/loremipsum-1/",
+      avatar_hash: "5c6a04b4",
+      email: "test@example.com",
+      full_title: "Forum team",
+      is_hiding_presence: false,
+      joined_on: "2015-05-09T16:13:33.973603Z",
+      limits_private_thread_invites_to: 0,
+      new_notifications: 0,
+      posts: 30,
+      rank: {
+        id: 1,
+
+        css_class: "team",
+        description: '<p>Lorem ipsum dolor met sit amet elit, si vis pacem para bellum.</p>\n<p>To help see <a href="http://wololo.com/something.php?page=2131">http://wololo.com/something.php?page=2131</a></p>',
+        is_tab: true,
+        name: "Forum team",
+        slug: "forum-team",
+        title: "Team"
+      },
+      short_title: "Team",
+      slug: "loremipsum",
+      subscribe_to_replied_threads: 2,
+      subscribe_to_started_threads: 1,
+      threads: 0,
+      title: "",
+      unread_private_threads: 0,
+      username: "LoremIpsum",
+
+      acl: {}
+    }
+  });
+
+  if (overrides) {
+    Object.assign(misago._context.user, overrides);
+  }
+}
+
+// global utility function for store mocking
+export function initEmptyStore(store) {
+  store.constructor();
+  store.addReducer('test', function(state={}, action=null) { return {}; }, {}); // jshint ignore:line
+  store.init();
+}
+
+export function snackbarStoreMock() {
+  return {
+    message: null,
+    _callback: null,
+
+    callback: function(callback) {
+      this._callback = callback;
+    },
+
+    dispatch: function(action) {
+      if (action.type === 'SHOW_SNACKBAR') {
+        this.message = {
+          message: action.message,
+          type: action.messageType
+        };
+
+        if (this._callback) {
+          window.setTimeout(() => {
+            this._callback(this.message);
+          }, 100);
+        }
+      }
+    }
+  };
+}
+
+// global init function for modal and dropdown services
+export function initModal(modal) {
+  $('#modal-mount').off();
+  modal.init(document.getElementById('modal-mount'));
+}
+
+export function initDropdown(dropdown) {
+  dropdown.init(document.getElementById('dropdown-mount'));
+}
+
+// global util for reseting snackbar
+export function snackbarClear(snackbar) {
+  // NOTE: Never ever cause situation when snackbar is triggered more than once
+  // in the single test, because this results in race condition within tests
+  // suite where one tests check's snackbar state before it has reopened with
+  // new message set by current test
+  if (snackbar._timeout) {
+    window.clearTimeout(snackbar._timeout);
+    snackbar._timeout = null;
+  }
+}
+
+// global util functions for events
+export function simulateClick(selector) {
+  if ($(selector).length) {
+    ReactTestUtils.Simulate.click($(selector).get(0));
+  } else {
+    throw 'selector "' + selector + '" did not match anything';
+  }
+}
+
+export function simulateSubmit(selector) {
+  if ($(selector).length) {
+    ReactTestUtils.Simulate.submit($(selector).get(0));
+  } else {
+    throw 'selector "' + selector + '" did not match anything';
+  }
+}
+
+export function simulateChange(selector, value) {
+  if ($(selector).length) {
+    $(selector).val(value);
+    ReactTestUtils.Simulate.change($(selector).get(0));
+  } else {
+    throw 'selector "' + selector + '" did not match anything';
+  }
+}
+
+export function afterAjax(callback) {
+  window.setTimeout(function() {
+    callback();
+  }, 200);
+}
+
+export function onElement(selector, callback) {
+  let _getElement = function() {
+    window.setTimeout(function() {
+      let element = $(selector);
+      if (element.length >= 1) {
+        callback(element);
+      } else {
+        _getElement();
+      }
+    }, 50);
+  };
+
+  _getElement();
+}

+ 2 - 1
frontend/tests/banned-page.js

@@ -3,6 +3,7 @@ import moment from 'moment';
 import misago from 'misago/index';
 import store from 'misago/services/store'; // jshint ignore:line
 import showBannedPage from 'misago/utils/banned-page';
+import * as testUtils from 'misago/utils/test-utils';
 
 let ban = {
   message: {
@@ -31,7 +32,7 @@ describe('Show Banned Page', function() {
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("renders banned page", function(done) {

+ 10 - 10
frontend/tests/captcha.js

@@ -1,9 +1,9 @@
 import assert from "assert";
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import ajax from 'misago/services/ajax';
 import { NoCaptcha, QACaptcha, ReCaptcha, Captcha } from 'misago/services/captcha';
 import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
 
 let captcha = null;
 let snackbarStore = null;
@@ -32,13 +32,13 @@ describe("NoCaptcha", function() {
 
 describe("QACaptcha", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -127,7 +127,7 @@ describe("QACaptcha", function() {
 
     captcha.load().then(function() {
       /* jshint ignore:start */
-      ReactDOM.render(
+      testUtils.render(
         <div>
           {captcha.component({
             form: {
@@ -147,7 +147,7 @@ describe("QACaptcha", function() {
             }
           })}
         </div>,
-        document.getElementById('test-mount')
+        'test-mount'
       );
       /* jshint ignore:end */
 
@@ -167,8 +167,8 @@ describe("ReCaptcha", function() {
   afterEach(function() {
     delete window.grecaptcha;
 
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -219,7 +219,7 @@ describe("ReCaptcha", function() {
 
     captcha.load().then(function() {
       /* jshint ignore:start */
-      ReactDOM.render(
+      testUtils.render(
         <div>
           {captcha.component({
             form: {
@@ -242,7 +242,7 @@ describe("ReCaptcha", function() {
             }
           })}
         </div>,
-        document.getElementById('test-mount')
+        'test-mount'
       );
       /* jshint ignore:end */
 

+ 7 - 10
frontend/tests/components/auth-message.js

@@ -1,19 +1,16 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import AuthMessage from 'misago/components/auth-message'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Auth Message", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders stateless', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <AuthMessage />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<AuthMessage />, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount .auth-message');
@@ -23,11 +20,11 @@ describe("Auth Message", function() {
 
   it('renders signed out', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <AuthMessage user={{username: 'Boberson'}}
                    signedOut={true}
                    signedIn={false} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -38,11 +35,11 @@ describe("Auth Message", function() {
 
   it('renders signed in', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <AuthMessage user={null}
                    signedOut={false}
                    signedIn={{username: 'Boberson'}} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 4 - 10
frontend/tests/components/avatar.js

@@ -1,19 +1,16 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import Avatar from 'misago/components/avatar'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Avatar", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders guest avatar', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <Avatar size="42" />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Avatar size="42" />, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount img.user-avatar');
@@ -28,10 +25,7 @@ describe("Avatar", function() {
       avatar_hash: 'aabbccddeeff'
     };
 
-    ReactDOM.render(
-      <Avatar user={user} size="42" />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Avatar user={user} size="42" />, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount img.user-avatar');

+ 12 - 12
frontend/tests/components/banned-page.js

@@ -1,19 +1,19 @@
 import assert from 'assert';
 import moment from 'moment'; // jshint ignore:line
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import BannedPage from 'misago/components/banned-page'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Banned page", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <BannedPage message={{html: '<p>Lorem ipsum!</p>'}} expires={null} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -25,9 +25,9 @@ describe("Banned page", function() {
 
   it('renders with fallback message', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <BannedPage message={{plain: 'Lorem ipsum plain!'}} expires={null} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -39,9 +39,9 @@ describe("Banned page", function() {
 
   it('renders with permanent expiration date', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <BannedPage message={{plain: 'Lorem ipsum plain!'}} expires={null} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -54,9 +54,9 @@ describe("Banned page", function() {
   it('renders with future expiration date', function() {
     /* jshint ignore:start */
     let expires = moment().add(7, 'days');
-    ReactDOM.render(
+    testUtils.render(
       <BannedPage message={{plain: 'Lorem ipsum plain!'}} expires={expires} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -69,9 +69,9 @@ describe("Banned page", function() {
   it('renders with past expiration date', function() {
     /* jshint ignore:start */
     let expires = moment().subtract(7, 'days');
-    ReactDOM.render(
+    testUtils.render(
       <BannedPage message={{plain: 'Lorem ipsum plain!'}} expires={expires} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 7 - 27
frontend/tests/components/button.js

@@ -1,21 +1,16 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import Button from 'misago/components/button'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Button", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <Button>
-        Lorem ipsum
-      </Button>,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Button>Lorem ipsum</Button>, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount button');
@@ -31,28 +26,18 @@ describe("Button", function() {
       done();
     }
 
-    ReactDOM.render(
-      <Button onClick={click}>
-        Lorem ipsum
-      </Button>,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Button onClick={click}>Lorem ipsum</Button>, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount button');
     assert.ok(element.length, "component rendered");
     assert.equal(element.attr('type'), 'button', "component is regular button");
-    window.simulateClick('#test-mount button');
+    testUtils.simulateClick('#test-mount button');
   });
 
   it('renders disabled', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <Button disabled={true}>
-        Lorem ipsum
-      </Button>,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Button disabled={true}>Lorem ipsum</Button>, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount button');
@@ -62,12 +47,7 @@ describe("Button", function() {
 
   it('renders loading', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <Button loading={true}>
-        Lorem ipsum
-      </Button>,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Button loading={true}>Lorem ipsum</Button>, 'test-mount');
     /* jshint ignore:end */
 
     let element = $('#test-mount button>.loader');

+ 14 - 14
frontend/tests/components/form-group.js

@@ -1,21 +1,21 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import FormGroup from 'misago/components/form-group'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Form Group", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  for="test_input">
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -33,14 +33,14 @@ describe("Form Group", function() {
 
   it('renders label and control classes', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  labelClass="test-label"
                  controlClass="test-control"
                  for="test_input">
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -55,13 +55,13 @@ describe("Form Group", function() {
 
   it('renders positive feedback', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  for="test_input"
                  validation={null}>
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -79,13 +79,13 @@ describe("Form Group", function() {
 
   it('renders negative feedback', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  for="test_input"
                  validation={["First issue.", "Second issue."]}>
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -114,13 +114,13 @@ describe("Form Group", function() {
 
   it('renders help text', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  for="test_input"
                  helpText="Lorem ipsum dolor met.">
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -134,13 +134,13 @@ describe("Form Group", function() {
 
   it('renders extra', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <FormGroup label="Lorem Ipsum"
                  for="test_input"
                  extra={<p id="row-extra">Extra!!!</p>}>
         <input name="lorem" type="text" />
       </FormGroup>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 3 - 6
frontend/tests/components/form.js

@@ -1,8 +1,8 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import Form from 'misago/components/form';
 import { email, minLength } from 'misago/utils/validators'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 var form = null;
 
@@ -39,15 +39,12 @@ class TestForm extends Form { // jshint ignore:line
 describe("Form", function() {
   beforeEach(function() {
     /* jshint ignore:start */
-    form = ReactDOM.render(
-      <TestForm />,
-      document.getElementById('test-mount')
-    );
+    form = testUtils.render(<TestForm />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("validates individual field", function() {

+ 3 - 6
frontend/tests/components/loader.js

@@ -1,19 +1,16 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import Loader from 'misago/components/loader'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Loader", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
-      <Loader />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<Loader />, 'test-mount');
     /* jshint ignore:end */
 
     assert.ok($('#test-mount .loader .loader-spinning-wheel').length,

+ 12 - 12
frontend/tests/components/password-strength.js

@@ -1,12 +1,12 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import PasswordStrength from 'misago/components/password-strength'; // jshint ignore:line
 import zxcvbn from 'misago/services/zxcvbn';
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Password Strength", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
     delete window.zxcvbn;
   });
 
@@ -29,9 +29,9 @@ describe("Password Strength", function() {
     zxcvbn.load();
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <PasswordStrength password="very-weak" inputs={['a', 'b', 'c']} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -71,9 +71,9 @@ describe("Password Strength", function() {
     zxcvbn.load();
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <PasswordStrength password="weak" inputs={['a', 'b', 'c']} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -113,9 +113,9 @@ describe("Password Strength", function() {
     zxcvbn.load();
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <PasswordStrength password="average" inputs={['a', 'b', 'c']} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -155,9 +155,9 @@ describe("Password Strength", function() {
     zxcvbn.load();
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <PasswordStrength password="stronk" inputs={['a', 'b', 'c']} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -197,9 +197,9 @@ describe("Password Strength", function() {
     zxcvbn.load();
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <PasswordStrength password="very-stronk" inputs={['a', 'b', 'c']} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 11 - 14
frontend/tests/components/register-button.js

@@ -1,33 +1,30 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import RegisterButton from 'misago/components/register-button'; // jshint ignore:line
 import misago from 'misago/index';
 import captcha from 'misago/services/captcha';
 import modal from 'misago/services/modal';
 import snackbar from 'misago/services/snackbar';
 import zxcvbn from 'misago/services/zxcvbn';
+import * as testUtils from 'misago/utils/test-utils';
 
 let snackbarStore = null;
 
 describe("RegisterButton", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
-    window.initModal(modal);
-    window.contextClear(misago);
+    testUtils.initModal(modal);
+    testUtils.contextClear(misago);
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <RegisterButton />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<RegisterButton />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
     delete window.zxcvbn;
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -36,7 +33,7 @@ describe("RegisterButton", function() {
   });
 
   it('alerts about closed registration', function(done) {
-    window.misago._context = {
+    misago._context = {
       SETTINGS: {
         account_activation: 'closed'
       }
@@ -50,11 +47,11 @@ describe("RegisterButton", function() {
       done();
     });
 
-    window.simulateClick('#test-mount button');
+    testUtils.simulateClick('#test-mount button');
   });
 
   it('opens registration modal', function(done) {
-    window.misago._context = {
+    misago._context = {
       SETTINGS: {
         captcha_type: 'no',
         account_activation: 'none'
@@ -73,9 +70,9 @@ describe("RegisterButton", function() {
       }
     });
 
-    window.simulateClick('#test-mount button');
+    testUtils.simulateClick('#test-mount button');
 
-    window.onElement('#modal-mount .modal-register', function() {
+    testUtils.onElement('#modal-mount .modal-register', function() {
       let element = $('#modal-mount .modal-register');
       assert.ok(element.length, "registration modal was opened");
 

+ 36 - 36
frontend/tests/components/register.js

@@ -1,19 +1,19 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import misago from 'misago/index';
 import { RegisterForm, RegisterComplete } from 'misago/components/register'; // jshint ignore:line
 import modal from 'misago/services/modal';
 import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
 
 let component = null;
 let snackbarStore = null;
 
 describe("Register Form", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
-    window.initModal(modal);
+    testUtils.initModal(modal);
 
     window.zxcvbn = function() {
       return {
@@ -36,17 +36,17 @@ describe("Register Form", function() {
     };
 
     /* jshint ignore:start */
-    component = ReactDOM.render(
+    component = testUtils.render(
       <RegisterForm />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
   });
 
   afterEach(function() {
     delete window.zxcvbn;
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -71,7 +71,7 @@ describe("Register Form", function() {
       done();
     });
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles invalid submit", function(done) {
@@ -83,11 +83,11 @@ describe("Register Form", function() {
       done();
     });
 
-    window.simulateChange('#id_username', 'lo');
-    window.simulateChange('#id_email', 'nope');
-    window.simulateChange('#id_password', 'sh');
+    testUtils.simulateChange('#id_username', 'lo');
+    testUtils.simulateChange('#id_email', 'nope');
+    testUtils.simulateChange('#id_password', 'sh');
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend error", function(done) {
@@ -104,11 +104,11 @@ describe("Register Form", function() {
       status: 500
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_email', 'lorem@ipsum.com');
-    window.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_email', 'lorem@ipsum.com');
+    testUtils.simulateChange('#id_password', 'pass1234');
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles rejected data", function(done) {
@@ -139,11 +139,11 @@ describe("Register Form", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_email', 'lorem@ipsum.com');
-    window.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_email', 'lorem@ipsum.com');
+    testUtils.simulateChange('#id_password', 'pass1234');
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("from banned IP", function(done) {
@@ -161,13 +161,13 @@ describe("Register Form", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_email', 'lorem@ipsum.com');
-    window.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_email', 'lorem@ipsum.com');
+    testUtils.simulateChange('#id_password', 'pass1234');
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
 
-    window.onElement('.page-error-banned .lead', function() {
+    testUtils.onElement('.page-error-banned .lead', function() {
       assert.equal(
         $('.page .message-body .lead p').text().trim(),
         "Your ip is banned from registering.",
@@ -188,9 +188,9 @@ describe("Register Form", function() {
       done();
     };
 
-    component = ReactDOM.render(
+    component = testUtils.render(
       <RegisterForm callback={callback}/>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -204,26 +204,26 @@ describe("Register Form", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_email', 'lorem@ipsum.com');
-    window.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_email', 'lorem@ipsum.com');
+    testUtils.simulateChange('#id_password', 'pass1234');
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 });
 
 describe("Register Complete", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("renders user-activated message", function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <RegisterComplete activation="user"
                         username="Bob"
                         email="bob@boberson.com" />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -241,11 +241,11 @@ describe("Register Complete", function() {
 
   it("renders admin-activated message", function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <RegisterComplete activation="admin"
                         username="Bob"
                         email="bob@boberson.com" />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 203 - 0
frontend/tests/components/request-activation-link.js

@@ -0,0 +1,203 @@
+import assert from 'assert';
+import React from 'react'; // jshint ignore:line
+import misago from 'misago/index';
+import { RequestLinkForm, LinkSent } from 'misago/components/request-activation-link'; // jshint ignore:line
+import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
+
+let snackbarStore = null;
+
+describe("Request Activation Link Form", function() {
+  beforeEach(function() {
+    snackbarStore = testUtils.snackbarStoreMock();
+    snackbar.init(snackbarStore);
+
+    misago._context = {
+      'SETTINGS': {
+        'forum_name': 'Test forum'
+      },
+      'SEND_ACTIVATION_API': '/test-api/send-activation/'
+    };
+
+    /* jshint ignore:start */
+    testUtils.render(<RequestLinkForm />, 'test-mount');
+    /* jshint ignore:end */
+  });
+
+  afterEach(function() {
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
+    $.mockjax.clear();
+  });
+
+  it("renders", function() {
+    let element = $('#test-mount .well-form-request-activation-link');
+    assert.ok(element.length, "component renders");
+  });
+
+  it("handles empty submit", function(done) {
+    snackbarStore.callback(function(message) {
+      assert.deepEqual(message, {
+        message: "Enter a valid email address.",
+        type: 'error'
+      }, "form brought error about no input");
+      done();
+    });
+
+    testUtils.simulateSubmit('#test-mount form');
+  });
+
+  it("handles invalid submit", function(done) {
+    snackbarStore.callback(function(message) {
+      assert.deepEqual(message, {
+        message: "Enter a valid email address.",
+        type: 'error'
+      }, "form brought error about invalid input");
+      done();
+    });
+
+    testUtils.simulateChange('#test-mount input', 'loremipsum');
+    testUtils.simulateSubmit('#test-mount form');
+  });
+
+  it("handles backend error", function(done) {
+    snackbarStore.callback(function(message) {
+      assert.deepEqual(message, {
+        message: "Unknown error has occured.",
+        type: 'error'
+      }, "form raised alert about backend error");
+      done();
+    });
+
+    $.mockjax({
+      url: '/test-api/send-activation/',
+      status: 500
+    });
+
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
+  });
+
+  it("handles backend rejection", function(done) {
+    snackbarStore.callback(function(message) {
+      assert.deepEqual(message, {
+        message: "Nope nope nope!",
+        type: 'error'
+      }, "form raised alert about backend rejection");
+      done();
+    });
+
+    $.mockjax({
+      url: '/test-api/send-activation/',
+      status: 400,
+      responseText: {
+        detail: "Nope nope nope!"
+      }
+    });
+
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
+  });
+
+  it("handles backend info", function(done) {
+    snackbarStore.callback(function(message) {
+      assert.deepEqual(message, {
+        message: "Your account is already active!",
+        type: 'info'
+      }, "form raised alert about backend info");
+      done();
+    });
+
+    $.mockjax({
+      url: '/test-api/send-activation/',
+      status: 400,
+      responseText: {
+        code: 'already_active',
+        detail: "Your account is already active!"
+      }
+    });
+
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
+  });
+
+  it("from banned IP", function(done) {
+    $.mockjax({
+      url: '/test-api/send-activation/',
+      status: 403,
+      responseText: {
+        'ban': {
+          'expires_on': null,
+          'message': {
+            'plain': 'Your ip is banned for spamming.',
+            'html': '<p>Your ip is banned for spamming.</p>',
+          }
+        }
+      }
+    });
+
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
+
+    testUtils.onElement('.page-error-banned .lead', function() {
+      assert.equal(
+        $('.page .message-body .lead p').text().trim(),
+        "Your ip is banned for spamming.",
+        "displayed error banned page with ban message.");
+
+      done();
+    });
+  });
+
+  it("handles success", function(done) { // jshint ignore:line
+    $.mockjax({
+      url: '/test-api/send-activation/',
+      status: 200,
+      responseText: {
+        'username': 'Bob',
+        'email': 'bob@boberson.com'
+      }
+    });
+
+    /* jshint ignore:start */
+    let callback = function(apiResponse) {
+      assert.deepEqual(apiResponse, {
+        'username': 'Bob',
+        'email': 'bob@boberson.com'
+      }, "callback function was called on ajax success");
+      done();
+    };
+
+    testUtils.render(<RequestLinkForm callback={callback} />, 'test-mount');
+    /* jshint ignore:end */
+
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
+  });
+});
+
+describe("Activation Link Sent", function() {
+  afterEach(function() {
+    testUtils.emptyTestContainers();
+  });
+
+  it("renders message", function(done) { // jshint ignore:line
+    /* jshint ignore:start */
+    let callback = function() {
+      assert.ok(true, "callback function was called on button press");
+      done();
+    };
+
+    testUtils.render(<LinkSent user={{email: 'bob@boberson.com' }} callback={callback} />, 'test-mount');
+    /* jshint ignore:end */
+
+    let element = $('#test-mount .well-done');
+    assert.ok(element.length, "component renders");
+
+    assert.equal(element.find('p').text().trim(),
+      "Activation link was sent to bob@boberson.com",
+      "component renders valid message");
+
+    testUtils.simulateClick('#test-mount .btn-primary');
+  });
+});

+ 31 - 37
frontend/tests/components/request-password-reset.js

@@ -1,15 +1,15 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import misago from 'misago/index';
 import { RequestResetForm, LinkSent, AccountInactivePage } from 'misago/components/request-password-reset'; // jshint ignore:line
 import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
 
 let snackbarStore = null;
 
 describe("Request Password Reset Form", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
 
     misago._context = {
@@ -20,16 +20,13 @@ describe("Request Password Reset Form", function() {
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <RequestResetForm />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<RequestResetForm />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -47,7 +44,7 @@ describe("Request Password Reset Form", function() {
       done();
     });
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles invalid submit", function(done) {
@@ -59,8 +56,8 @@ describe("Request Password Reset Form", function() {
       done();
     });
 
-    window.simulateChange('#test-mount input', 'loremipsum');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'loremipsum');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend error", function(done) {
@@ -77,8 +74,8 @@ describe("Request Password Reset Form", function() {
       status: 500
     });
 
-    window.simulateChange('#test-mount input', 'lorem@ipsum.com');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend rejection", function(done) {
@@ -98,8 +95,8 @@ describe("Request Password Reset Form", function() {
       }
     });
 
-    window.simulateChange('#test-mount input', 'lorem@ipsum.com');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("displays activation required message", function(done) { // jshint ignore:line
@@ -125,14 +122,14 @@ describe("Request Password Reset Form", function() {
       done();
     };
 
-    ReactDOM.render(
+    testUtils.render(
       <RequestResetForm showInactivePage={showInactivePage}/>,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
-    window.simulateChange('#test-mount input', 'lorem@ipsum.com');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("from banned IP", function(done) {
@@ -150,10 +147,10 @@ describe("Request Password Reset Form", function() {
       }
     });
 
-    window.simulateChange('#test-mount input', 'lorem@ipsum.com');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
 
-    window.onElement('.page-error-banned .lead', function() {
+    testUtils.onElement('.page-error-banned .lead', function() {
       assert.equal(
         $('.page .message-body .lead p').text().trim(),
         "Your ip is banned for spamming.",
@@ -182,20 +179,17 @@ describe("Request Password Reset Form", function() {
       done();
     };
 
-    ReactDOM.render(
-      <RequestResetForm callback={callback} />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<RequestResetForm callback={callback} />, 'test-mount');
     /* jshint ignore:end */
 
-    window.simulateChange('#test-mount input', 'lorem@ipsum.com');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'lorem@ipsum.com');
+    testUtils.simulateSubmit('#test-mount form');
   });
 });
 
 describe("Reset Link Sent", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("renders message", function(done) { // jshint ignore:line
@@ -205,10 +199,10 @@ describe("Reset Link Sent", function() {
       done();
     };
 
-    ReactDOM.render(
+    testUtils.render(
       <LinkSent user={{email: 'bob@boberson.com' }}
                 callback={callback} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -219,7 +213,7 @@ describe("Reset Link Sent", function() {
       "Reset password link was sent to bob@boberson.com",
       "component renders valid message");
 
-    window.simulateClick('#test-mount .btn-primary');
+    testUtils.simulateClick('#test-mount .btn-primary');
   });
 });
 
@@ -231,15 +225,15 @@ describe("Account Inactive Page", function() {
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("renders page for user-activated user", function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <AccountInactivePage activation='inactive_user'
                            message="Lorem ipsum dolor met." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -257,10 +251,10 @@ describe("Account Inactive Page", function() {
 
   it("renders page for admin-activated user", function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <AccountInactivePage activation='inactive_admin'
                            message="Lorem ipsum dolor met admin." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 

+ 23 - 32
frontend/tests/components/reset-password-form.js

@@ -1,16 +1,16 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import misago from 'misago/index';
 import { ResetPasswordForm, PasswordChangedPage } from 'misago/components/reset-password-form'; // jshint ignore:line
 import modal from 'misago/services/modal';
 import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
 
 let snackbarStore = null;
 
 describe("Reset Password Form", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
 
     misago._context = {
@@ -22,16 +22,13 @@ describe("Reset Password Form", function() {
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <ResetPasswordForm />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<ResetPasswordForm />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -49,7 +46,7 @@ describe("Reset Password Form", function() {
       done();
     });
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles invalid submit", function(done) {
@@ -61,8 +58,8 @@ describe("Reset Password Form", function() {
       done();
     });
 
-    window.simulateChange('#test-mount input', 'abc');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'abc');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend error", function(done) {
@@ -79,8 +76,8 @@ describe("Reset Password Form", function() {
       status: 500
     });
 
-    window.simulateChange('#test-mount input', 'Som3L33tP455');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'Som3L33tP455');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend rejection", function(done) {
@@ -100,8 +97,8 @@ describe("Reset Password Form", function() {
       }
     });
 
-    window.simulateChange('#test-mount input', 'Som3L33tP455');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'Som3L33tP455');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("from banned IP", function(done) {
@@ -119,10 +116,10 @@ describe("Reset Password Form", function() {
       }
     });
 
-    window.simulateChange('#test-mount input', 'Som3L33tP455');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'Som3L33tP455');
+    testUtils.simulateSubmit('#test-mount form');
 
-    window.onElement('.page-error-banned .lead', function() {
+    testUtils.onElement('.page-error-banned .lead', function() {
       assert.equal(
         $('.page .message-body .lead p').text().trim(),
         "Your ip is banned for spamming.",
@@ -149,35 +146,29 @@ describe("Reset Password Form", function() {
       done();
     };
 
-    ReactDOM.render(
-      <ResetPasswordForm callback={callback} />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<ResetPasswordForm callback={callback} />, 'test-mount');
     /* jshint ignore:end */
 
-    window.simulateChange('#test-mount input', 'Som3L33tP455');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#test-mount input', 'Som3L33tP455');
+    testUtils.simulateSubmit('#test-mount form');
   });
 });
 
 describe("Password Changed Page", function() {
   beforeEach(function() {
-    window.initModal(modal);
+    testUtils.initModal(modal);
 
     misago._context = {
       'FORGOTTEN_PASSWORD_URL': '/forgotten-password/'
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <PasswordChangedPage user={{username: 'BobBoberson'}} />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<PasswordChangedPage user={{username: 'BobBoberson'}} />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it("renders", function() {
@@ -191,9 +182,9 @@ describe("Password Changed Page", function() {
   });
 
   it('opens sign in modal on click', function(done) {
-    window.simulateClick('#test-mount .btn-primary');
+    testUtils.simulateClick('#test-mount .btn-primary');
 
-    window.onElement('#modal-mount .modal-sign-in', function() {
+    testUtils.onElement('#modal-mount .modal-sign-in', function() {
       assert.ok(true, "sign in modal was displayed");
       done();
     });

+ 32 - 35
frontend/tests/components/sign-in.js

@@ -1,18 +1,18 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import misago from 'misago/index';
 import SignIn from 'misago/components/sign-in'; // jshint ignore:line
 import modal from 'misago/services/modal';
 import snackbar from 'misago/services/snackbar';
+import * as testUtils from 'misago/utils/test-utils';
 
 let snackbarStore = null;
 
 describe("Sign In", function() {
   beforeEach(function() {
-    snackbarStore = window.snackbarStoreMock();
+    snackbarStore = testUtils.snackbarStoreMock();
     snackbar.init(snackbarStore);
-    window.initModal(modal);
+    testUtils.initModal(modal);
 
     misago._context = {
       'SETTINGS': {
@@ -25,16 +25,13 @@ describe("Sign In", function() {
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <SignIn />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<SignIn />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
-    window.snackbarClear(snackbar);
+    testUtils.emptyTestContainers();
+    testUtils.snackbarClear(snackbar);
     $.mockjax.clear();
   });
 
@@ -59,7 +56,7 @@ describe("Sign In", function() {
       done();
     });
 
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles partial submit", function(done) {
@@ -71,8 +68,8 @@ describe("Sign In", function() {
       done();
     });
 
-    window.simulateChange('#id_username', 'loremipsum');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'loremipsum');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles backend error", function(done) {
@@ -89,9 +86,9 @@ describe("Sign In", function() {
       status: 500
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("handles invalid credentials", function(done) {
@@ -114,9 +111,9 @@ describe("Sign In", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("to admin-activated account", function(done) {
@@ -139,9 +136,9 @@ describe("Sign In", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("to user-activated account", function(done) {
@@ -171,9 +168,9 @@ describe("Sign In", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
   });
 
   it("from banned IP", function(done) {
@@ -191,11 +188,11 @@ describe("Sign In", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
 
-    window.onElement('.page-error-banned .lead', function() {
+    testUtils.onElement('.page-error-banned .lead', function() {
       assert.equal(
         $('.page .message-body .lead p').text().trim(),
         "Your ip is banned for spamming.",
@@ -221,11 +218,11 @@ describe("Sign In", function() {
       }
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
 
-    window.onElement('.page-error-banned .lead', function() {
+    testUtils.onElement('.page-error-banned .lead', function() {
       assert.equal(
         $('.page .message-body .lead p').text().trim(),
         "You are banned for trolling.",
@@ -261,8 +258,8 @@ describe("Sign In", function() {
       return false;
     });
 
-    window.simulateChange('#id_username', 'SomeFake');
-    window.simulateChange('#id_password', 'pass1234');
-    window.simulateSubmit('#test-mount form');
+    testUtils.simulateChange('#id_username', 'SomeFake');
+    testUtils.simulateChange('#id_password', 'pass1234');
+    testUtils.simulateSubmit('#test-mount form');
   });
 });

+ 12 - 12
frontend/tests/components/snackbar.js

@@ -1,28 +1,28 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import { Snackbar } from 'misago/components/snackbar'; // jshint ignore:line
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("Snackbar", function() {
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <Snackbar isVisible={false} message="" type="info" />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
     assert.ok($('.alerts-snackbar').hasClass('out'), "component is hidden");
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <Snackbar isVisible={true} type="success"
                 message="Lorem ipsum dolor met." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
 
@@ -35,30 +35,30 @@ describe("Snackbar", function() {
       "message is inserted");
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <Snackbar isVisible={true} type="info"
                 message="Lorem ipsum dolor met." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
     assert.ok($('.alerts-snackbar p').hasClass('alert-info'),
       "component has alert-info class");
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <Snackbar isVisible={true} type="warning"
                 message="Lorem ipsum dolor met." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
     assert.ok($('.alerts-snackbar p').hasClass('alert-warning'),
       "component has alert-warning class");
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <Snackbar isVisible={true} type="error"
                 message="Lorem ipsum dolor met." />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
     assert.ok($('.alerts-snackbar p').hasClass('alert-danger'),

+ 20 - 29
frontend/tests/components/user-menu/guest-menu.js

@@ -1,32 +1,29 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import { GuestMenu, GuestNav, CompactGuestNav } from 'misago/components/user-menu/guest-nav'; // jshint ignore:line
 import misago from 'misago/index';
 import dropdown from 'misago/services/mobile-navbar-dropdown';
 import modal from 'misago/services/modal';
 import store from 'misago/services/store';
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("GuestMenu", function() {
   beforeEach(function() {
-    window.initEmptyStore(store);
-    window.initDropdown(dropdown);
-    window.initModal(modal);
+    testUtils.initEmptyStore(store);
+    testUtils.initDropdown(dropdown);
+    testUtils.initModal(modal);
 
     misago._context = {
       'FORGOTTEN_PASSWORD_URL': '/forgotten-password/'
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <GuestMenu />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<GuestMenu />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -35,9 +32,9 @@ describe("GuestMenu", function() {
   });
 
   it('opens sign in modal on click', function(done) {
-    window.simulateClick('#test-mount .btn-default');
+    testUtils.simulateClick('#test-mount .btn-default');
 
-    window.onElement('#modal-mount .modal-sign-in', function() {
+    testUtils.onElement('#modal-mount .modal-sign-in', function() {
       assert.ok(true, "sign in modal was displayed");
       done();
     });
@@ -46,24 +43,21 @@ describe("GuestMenu", function() {
 
 describe("GuestNav", function() {
   beforeEach(function() {
-    window.initEmptyStore(store);
-    window.initDropdown(dropdown);
-    window.initModal(modal);
+    testUtils.initEmptyStore(store);
+    testUtils.initDropdown(dropdown);
+    testUtils.initModal(modal);
 
     misago._context = {
       'FORGOTTEN_PASSWORD_URL': '/forgotten-password/'
     };
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <GuestNav />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<GuestNav />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -72,9 +66,9 @@ describe("GuestNav", function() {
   });
 
   it('opens sign in modal on click', function(done) {
-    window.simulateClick('#test-mount .btn-default');
+    testUtils.simulateClick('#test-mount .btn-default');
 
-    window.onElement('#modal-mount .modal-sign-in', function() {
+    testUtils.onElement('#modal-mount .modal-sign-in', function() {
       assert.ok(true, "sign in modal was displayed");
       done();
     });
@@ -83,19 +77,16 @@ describe("GuestNav", function() {
 
 describe("CompactGuestNav", function() {
   beforeEach(function() {
-    window.initEmptyStore(store);
-    window.initDropdown(dropdown);
+    testUtils.initEmptyStore(store);
+    testUtils.initDropdown(dropdown);
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <CompactGuestNav />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<CompactGuestNav />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -104,7 +95,7 @@ describe("CompactGuestNav", function() {
   });
 
   it('opens dropdown on click', function() {
-    window.simulateClick('#test-mount button');
+    testUtils.simulateClick('#test-mount button');
 
     let element = $('#dropdown-mount>.dropdown-menu');
     assert.ok(element.length, "component opened dropdown");

+ 20 - 26
frontend/tests/components/user-menu/user-menu.js

@@ -1,29 +1,26 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import { UserMenu, UserNav, CompactUserNav } from 'misago/components/user-menu/user-nav'; // jshint ignore:line
 import misago from 'misago/index';
 import dropdown from 'misago/services/mobile-navbar-dropdown';
 import store from 'misago/services/store';
+import * as testUtils from 'misago/utils/test-utils';
 
 describe("UserMenu", function() {
   beforeEach(function() {
-    window.contextClear(misago);
-    window.contextAuthenticated(misago);
+    testUtils.contextClear(misago);
+    testUtils.contextAuthenticated(misago);
 
-    window.initEmptyStore(store);
-    window.initDropdown(dropdown);
+    testUtils.initEmptyStore(store);
+    testUtils.initDropdown(dropdown);
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <UserMenu user={misago._context.user} />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<UserMenu user={misago._context.user} />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -34,22 +31,19 @@ describe("UserMenu", function() {
 
 describe("UserNav", function() {
   beforeEach(function() {
-    window.contextClear(misago);
-    window.contextAuthenticated(misago);
+    testUtils.contextClear(misago);
+    testUtils.contextAuthenticated(misago);
 
-    window.initEmptyStore(store);
-    window.initDropdown(dropdown);
+    testUtils.initEmptyStore(store);
+    testUtils.initDropdown(dropdown);
 
     /* jshint ignore:start */
-    ReactDOM.render(
-      <UserNav user={misago._context.user} />,
-      document.getElementById('test-mount')
-    );
+    testUtils.render(<UserNav user={misago._context.user} />, 'test-mount');
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -60,8 +54,8 @@ describe("UserNav", function() {
 
 describe("CompactUserNav", function() {
   beforeEach(function() {
-    window.contextClear(misago);
-    window.contextAuthenticated(misago);
+    testUtils.contextClear(misago);
+    testUtils.contextAuthenticated(misago);
 
     store.constructor();
     store.addReducer('auth', function(state={}, action=null){
@@ -76,18 +70,18 @@ describe("CompactUserNav", function() {
     });
     store.init();
 
-    window.initDropdown(dropdown);
+    testUtils.initDropdown(dropdown);
 
     /* jshint ignore:start */
-    ReactDOM.render(
+    testUtils.render(
       <CompactUserNav user={misago._context.user} />,
-      document.getElementById('test-mount')
+      'test-mount'
     );
     /* jshint ignore:end */
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('renders', function() {
@@ -96,7 +90,7 @@ describe("CompactUserNav", function() {
   });
 
   it('opens dropdown on click', function() {
-    window.simulateClick('#test-mount button');
+    testUtils.simulateClick('#test-mount button');
 
     let element = $('#dropdown-mount>.user-dropdown');
     assert.ok(element.length, "component opened dropdown");

+ 3 - 3
frontend/tests/modal.js

@@ -1,8 +1,8 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import store from 'misago/services/store';
 import { Modal } from 'misago/services/modal';
+import * as testUtils from 'misago/utils/test-utils';
 
 var modal = null;
 
@@ -53,11 +53,11 @@ describe("Modal", function() {
     modal = new Modal();
     modal.init(document.getElementById('modal-mount'));
 
-    window.initEmptyStore(store);
+    testUtils.initEmptyStore(store);
   });
 
   afterEach(function() {
-    window.emptyTestContainers();
+    testUtils.emptyTestContainers();
   });
 
   it('shows component', function(done) {

+ 4 - 8
frontend/tests/navbar-dropdown.js

@@ -1,8 +1,8 @@
 import assert from 'assert';
 import React from 'react'; // jshint ignore:line
-import ReactDOM from 'react-dom'; // jshint ignore:line
 import store from 'misago/services/store';
 import { MobileNavbarDropdown } from 'misago/services/mobile-navbar-dropdown';
+import * as testUtils from 'misago/utils/test-utils';
 
 var dropdown = null;
 
@@ -37,11 +37,7 @@ describe("Dropdown", function() {
   });
 
   afterEach(function() {
-    console.log('test(): ' + ReactDOM.__misago);
-    window.emptyTestContainers();
-    console.log('================');
-    console.log(document.getElementById('dropdown-mount').innerHTML);
-    console.log('================');
+    testUtils.emptyTestContainers();
   });
 
   it('shows component', function(done) {
@@ -53,7 +49,7 @@ describe("Dropdown", function() {
       done();
     }, 100);
   });
-/*
+
   it('shows and cycles component', function(done) {
     dropdown.show(TestComponentA);
 
@@ -69,5 +65,5 @@ describe("Dropdown", function() {
       assert.ok(element.length, "component was toggled");
       done();
     }, 300);
-  });*/
+  });
 });