diff --git a/.babelrc b/.babelrc index 8c7b66269d..6ba0e0dae0 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { "presets": ["react", "es2015", "es2016"], - "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"] + "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports"] } diff --git a/package.json b/package.json index 83d9bd16a8..2b1d6e82d2 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,13 @@ "lintall": "eslint src/ test/", "clean": "rimraf lib webapp electron_app/dist", "prepublish": "npm run build:compile", - "test": "karma start --single-run=true --autoWatch=false --browsers ChromeHeadless --colors=false", + "test": "karma start --single-run=true --autoWatch=false --browsers ChromeHeadless", "test-multi": "karma start" }, "dependencies": { "babel-polyfill": "^6.5.0", "babel-runtime": "^6.11.6", + "bluebird": "^3.5.0", "browser-request": "^0.3.3", "classnames": "^2.1.2", "draft-js": "^0.8.1", @@ -69,11 +70,10 @@ "matrix-react-sdk": "0.9.7", "modernizr": "^3.1.0", "pako": "^1.0.5", - "q": "^1.4.1", - "react": "^15.4.0", + "react": "^15.6.0", "react-dnd": "^2.1.4", "react-dnd-html5-backend": "^2.1.2", - "react-dom": "^15.4.0", + "react-dom": "^15.6.0", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", "sanitize-html": "^1.11.1", "text-encoding-utf-8": "^1.0.1", @@ -88,7 +88,7 @@ "babel-eslint": "^6.1.0", "babel-loader": "^6.2.5", "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-transform-async-to-generator": "^6.16.0", + "babel-plugin-transform-async-to-bluebird": "^1.1.1", "babel-plugin-transform-class-properties": "^6.16.0", "babel-plugin-transform-object-rest-spread": "^6.16.0", "babel-plugin-transform-runtime": "^6.15.0", @@ -120,7 +120,8 @@ "karma-junit-reporter": "^0.4.1", "karma-mocha": "^0.2.2", "karma-webpack": "^1.7.0", - "matrix-mock-request": "^1.0.0", + "matrix-mock-request": "^1.2.0", + "matrix-react-test-utils": "^0.2.0", "minimist": "^1.2.0", "mkdirp": "^0.5.1", "mocha": "^2.4.5", @@ -134,7 +135,7 @@ "postcss-simple-vars": "^3.0.0", "postcss-strip-inline-comments": "^0.1.5", "react-addons-perf": "^15.4.0", - "react-addons-test-utils": "^15.4.0", + "react-addons-test-utils": "^15.6.0", "rimraf": "^2.4.3", "source-map-loader": "^0.1.5", "webpack": "^1.12.14", diff --git a/src/VectorConferenceHandler.js b/src/VectorConferenceHandler.js index f34a7b732b..933f59937e 100644 --- a/src/VectorConferenceHandler.js +++ b/src/VectorConferenceHandler.js @@ -16,7 +16,7 @@ limitations under the License. "use strict"; -var q = require("q"); +import Promise from 'bluebird'; var Matrix = require("matrix-js-sdk"); var Room = Matrix.Room; var CallHandler = require('matrix-react-sdk/lib/CallHandler'); @@ -53,11 +53,11 @@ ConferenceCall.prototype._joinConferenceUser = function() { // Make sure the conference user is in the group chat room var groupRoom = this.client.getRoom(this.groupRoomId); if (!groupRoom) { - return q.reject("Bad group room ID"); + return Promise.reject("Bad group room ID"); } var member = groupRoom.getMember(this.confUserId); if (member && member.membership === "join") { - return q(); + return Promise.resolve(); } return this.client.invite(this.groupRoomId, this.confUserId); }; @@ -75,7 +75,7 @@ ConferenceCall.prototype._getConferenceUserRoom = function() { } } if (confRoom) { - return q(confRoom); + return Promise.resolve(confRoom); } return this.client.createRoom({ preset: "private_chat", diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index e7d68c39f6..ea3aa5a29c 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -28,7 +28,7 @@ var linkify = require('linkifyjs'); var linkifyString = require('linkifyjs/string'); var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix'); var sanitizeHtml = require('sanitize-html'); -var q = require('q'); +import Promise from 'bluebird'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; @@ -117,7 +117,7 @@ module.exports = React.createClass({ }, getMoreRooms: function() { - if (!MatrixClientPeg.get()) return q(); + if (!MatrixClientPeg.get()) return Promise.resolve(); const my_filter_string = this.state.filterString; const my_server = this.state.roomServer; @@ -266,7 +266,7 @@ module.exports = React.createClass({ }, onFillRequest: function(backwards) { - if (backwards || !this.nextBatch) return q(false); + if (backwards || !this.nextBatch) return Promise.resolve(false); return this.getMoreRooms(); }, diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index a7b19689fe..4d08e8332e 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; -import q from 'q'; +import Promise from 'bluebird'; import React from 'react'; import classNames from 'classnames'; import sdk from 'matrix-react-sdk'; @@ -61,7 +61,7 @@ module.exports = React.createClass({ const roomId = this.props.room.roomId; var cli = MatrixClientPeg.get(); if (!cli.isGuest()) { - q.delay(500).then(function() { + Promise.delay(500).then(function() { if (tagNameOff !== null && tagNameOff !== undefined) { cli.deleteRoomTag(roomId, tagNameOff).finally(function() { // Close the context menu @@ -212,7 +212,7 @@ module.exports = React.createClass({ RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => { // delay slightly so that the user can see their state change // before closing the menu - return q.delay(500).then(() => { + return Promise.delay(500).then(() => { if (this._unmounted) return; // Close the context menu if (this.props.onFinished) { diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 1b8de52d9d..451a28b8ee 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler'; -var q = require("q"); +import Promise from 'bluebird'; var sdk = require('matrix-react-sdk'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore'); @@ -236,7 +236,7 @@ module.exports = React.createClass({ } } - q.all(deferreds).done(function() { + Promise.all(deferreds).done(function() { self._refreshFromServer(); }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -306,7 +306,7 @@ module.exports = React.createClass({ } } - q.all(deferreds).done(function(resps) { + Promise.all(deferreds).done(function(resps) { self._refreshFromServer(); }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -361,7 +361,7 @@ module.exports = React.createClass({ } // Then, add the new ones - q.all(removeDeferreds).done(function(resps) { + Promise.all(removeDeferreds).done(function(resps) { var deferreds = []; var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState; @@ -399,7 +399,7 @@ module.exports = React.createClass({ } } - q.all(deferreds).done(function(resps) { + Promise.all(deferreds).done(function(resps) { self._refreshFromServer(); }, onError); }, onError); @@ -431,7 +431,9 @@ module.exports = React.createClass({ 'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions) ).then( function() { return cli.deletePushRule('global', kind, rule.rule_id); - }) + }).catch( (e) => { + console.warn(`Error when porting legacy rule: ${e}`); + }); }(kind, rule)); } } @@ -440,7 +442,7 @@ module.exports = React.createClass({ if (needsUpdate.length > 0) { // If some of the rules need to be ported then wait for the porting // to happen and then fetch the rules again. - return q.allSettled(needsUpdate).then( function() { + return Promise.all(needsUpdate).then( function() { return cli.getPushRules(); }); } else { @@ -594,7 +596,7 @@ module.exports = React.createClass({ self.setState({pushers: resp.pushers}); }); - q.all([pushRulesPromise, pushersPromise]).then(function() { + Promise.all([pushRulesPromise, pushersPromise]).then(function() { self.setState({ phase: self.phases.DISPLAY }); diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index a8ac18781b..00c19b13da 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -41,6 +41,7 @@ @import "./matrix-react-sdk/views/messages/_RoomAvatarEvent.scss"; @import "./matrix-react-sdk/views/messages/_TextualEvent.scss"; @import "./matrix-react-sdk/views/messages/_UnknownBody.scss"; +@import "./matrix-react-sdk/views/rooms/_AppsDrawer.scss"; @import "./matrix-react-sdk/views/rooms/_Autocomplete.scss"; @import "./matrix-react-sdk/views/rooms/_EntityTile.scss"; @import "./matrix-react-sdk/views/rooms/_EventTile.scss"; @@ -56,9 +57,7 @@ @import "./matrix-react-sdk/views/rooms/_RoomSettings.scss"; @import "./matrix-react-sdk/views/rooms/_RoomTile.scss"; @import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss"; -@import "./matrix-react-sdk/views/rooms/_TabCompleteBar.scss"; @import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss"; -@import "./matrix-react-sdk/views/rooms/_AppsDrawer.scss"; @import "./matrix-react-sdk/views/settings/_DevicesPanel.scss"; @import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss"; @import "./matrix-react-sdk/views/voip/_CallView.scss"; diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_RoomStatusBar.scss b/src/skins/vector/css/matrix-react-sdk/structures/_RoomStatusBar.scss index 1ae2a47c86..d4b425ee44 100644 --- a/src/skins/vector/css/matrix-react-sdk/structures/_RoomStatusBar.scss +++ b/src/skins/vector/css/matrix-react-sdk/structures/_RoomStatusBar.scss @@ -140,11 +140,6 @@ limitations under the License. cursor: pointer; } -.mx_RoomStatusBar_tabCompleteBar { - padding-top: 10px; - color: $primary-fg-color; -} - .mx_RoomStatusBar_typingBar { height: 50px; line-height: 50px; @@ -155,26 +150,6 @@ limitations under the License. display: block; } -.mx_RoomStatusBar_tabCompleteWrapper { - display: flex; - height: 26px; -} - -.mx_RoomStatusBar_tabCompleteWrapper .mx_TabCompleteBar { - flex: 1 1 auto; -} - -.mx_RoomStatusBar_tabCompleteEol { - flex: 0 0 auto; - color: $accent-color; -} - -.mx_RoomStatusBar_tabCompleteEol object { - vertical-align: middle; - margin-right: 8px; - margin-top: -2px; -} - .mx_MatrixChat_useCompactLayout { .mx_RoomStatusBar { min-height: 40px; diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss index 2dcd813692..1392ec6435 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss @@ -3,17 +3,21 @@ // --Matthew .mx_UserPill { - color: white; + color: $accent-fg-color; background-color: $accent-color; - padding: 2px 8px; + padding: 1px 5px 0px 2px; border-radius: 16px; } +.mx_UserPill img, .mx_RoomPill img { + vertical-align: -2px; + margin-right: 1px +} + .mx_RoomPill { - background-color: white; - color: $accent-color; - border: 1px solid $accent-color; - padding: 2px 8px; + background-color: $rte-room-pill-color; + color: $accent-fg-color; + padding: 1px 5px 0px 2px; border-radius: 16px; } diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss index 0fcabac1f4..7d1ac62818 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss @@ -76,6 +76,16 @@ limitations under the License. .mx_AppTileMenuBarWidget { // pointer-events: none; cursor: pointer; + width: 10px; + height: 10px; + padding: 1px; + transition-duration: 500ms; + border: 1px solid transparent; +} + +.mx_AppTileMenuBarWidget:hover { + border: 1px solid $primary-hairline-color; + border-radius: 2px; } .mx_AppTileBody iframe { diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MessageComposer.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MessageComposer.scss index 6c2216ddc6..43a5803099 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MessageComposer.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MessageComposer.scss @@ -100,6 +100,11 @@ limitations under the License. word-break: break-word; } +.mx_MessageComposer_input .DraftEditor-root .DraftEditor-editorContainer { + /* Ensure mx_UserPill and mx_RoomPill (see _RichText) are not obscured from the top */ + padding-top: 2px; +} + .mx_MessageComposer_input blockquote { color: $blockquote-fg-color; margin: 0 0 16px; diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_TabCompleteBar.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_TabCompleteBar.scss deleted file mode 100644 index 5dcbd21a47..0000000000 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_TabCompleteBar.scss +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_TabCompleteBar { - overflow: hidden; -} - -.mx_TabCompleteBar_item { - display: inline-block; - margin-right: 15px; - margin-bottom: 2px; - cursor: pointer; -} - -.mx_TabCompleteBar_command { - margin-right: 8px; - background-color: $accent-color; - padding-left: 8px; - padding-right: 8px; - padding-top: 2px; - padding-bottom: 2px; - margin-bottom: 6px; - border-radius: 30px; - position: relative; - top: 1px; -} - -.mx_TabCompleteBar_command .mx_TabCompleteBar_text { - opacity: 1.0; - vertical-align: initial; - color: $accent-fg-color; -} - -.mx_TabCompleteBar_item img { - margin-right: 8px; - vertical-align: middle; -} - -.mx_TabCompleteBar_text { - color: $primary-fg-color; - vertical-align: middle; - opacity: 0.5; -} diff --git a/src/skins/vector/css/themes/_base.scss b/src/skins/vector/css/themes/_base.scss index fc24af93e2..9701a48ffa 100644 --- a/src/skins/vector/css/themes/_base.scss +++ b/src/skins/vector/css/themes/_base.scss @@ -78,6 +78,7 @@ $voip-accept-color: #80f480; $rte-bg-color: #e9e9e9; $rte-code-bg-color: rgba(0, 0, 0, 0.04); +$rte-room-pill-color: #aaa; // ******************** diff --git a/src/vector/index.js b/src/vector/index.js index 81d329c004..c0cb64102e 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -65,7 +65,7 @@ var sdk = require("matrix-react-sdk"); const PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg"); sdk.loadSkin(require('../component-index')); var VectorConferenceHandler = require('../VectorConferenceHandler'); -var q = require('q'); +import Promise from 'bluebird'; var request = require('browser-request'); import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore'; import * as languageHandler from 'matrix-react-sdk/lib/languageHandler'; @@ -187,11 +187,11 @@ var makeRegistrationUrl = function(params) { window.addEventListener('hashchange', onHashChange); -function getConfig() { - let deferred = q.defer(); +function getConfig(configJsonFilename) { + let deferred = Promise.defer(); request( - { method: "GET", url: "config.json" }, + { method: "GET", url: configJsonFilename }, (err, response, body) => { if (err || response.status < 200 || response.status >= 300) { // Lack of a config isn't an error, we should @@ -261,10 +261,20 @@ async function loadApp() { } } + // Load the config file. First try to load up a domain-specific config of the + // form "config.$domain.json" and if that fails, fall back to config.json. let configJson; let configError; try { - configJson = await getConfig(); + try { + configJson = await getConfig(`config.${document.domain}.json`); + // 404s succeed with an empty json config, so check that there are keys + if (Object.keys(configJson).length === 0) { + throw new Error(); // throw to enter the catch + } + } catch (e) { + configJson = await getConfig("config.json"); + } } catch (e) { configError = e; } diff --git a/src/vector/platform/ElectronPlatform.js b/src/vector/platform/ElectronPlatform.js index 4f0ffae733..8e97f23862 100644 --- a/src/vector/platform/ElectronPlatform.js +++ b/src/vector/platform/ElectronPlatform.js @@ -20,7 +20,7 @@ limitations under the License. import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform'; import dis from 'matrix-react-sdk/lib/dispatcher'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; -import q from 'q'; +import Promise from 'bluebird'; import {remote, ipcRenderer} from 'electron'; import rageshake from '../rageshake'; @@ -173,7 +173,7 @@ export default class ElectronPlatform extends VectorBasePlatform { } getAppVersion(): Promise { - return q(remote.app.getVersion()); + return Promise.resolve(remote.app.getVersion()); } startUpdateCheck() { @@ -201,7 +201,7 @@ export default class ElectronPlatform extends VectorBasePlatform { isElectron(): boolean { return true; } requestNotificationPermission(): Promise { - return q('granted'); + return Promise.resolve('granted'); } reload() { diff --git a/src/vector/platform/WebPlatform.js b/src/vector/platform/WebPlatform.js index ae1e54b307..b88ee93f1d 100644 --- a/src/vector/platform/WebPlatform.js +++ b/src/vector/platform/WebPlatform.js @@ -21,7 +21,7 @@ import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform'; import request from 'browser-request'; import dis from 'matrix-react-sdk/lib/dispatcher.js'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; -import q from 'q'; +import Promise from 'bluebird'; import url from 'url'; import UAParser from 'ua-parser-js'; @@ -68,7 +68,7 @@ export default class WebPlatform extends VectorBasePlatform { // annoyingly, the latest spec says this returns a // promise, but this is only supported in Chrome 46 // and Firefox 47, so adapt the callback API. - const defer = q.defer(); + const defer = Promise.defer(); global.Notification.requestPermission((result) => { defer.resolve(result); }); @@ -103,7 +103,7 @@ export default class WebPlatform extends VectorBasePlatform { } _getVersion(): Promise { - const deferred = q.defer(); + const deferred = Promise.defer(); // We add a cachebuster to the request to make sure that we know about // the most recent version on the origin server. That might not @@ -132,7 +132,7 @@ export default class WebPlatform extends VectorBasePlatform { getAppVersion(): Promise { if (this.runningVersion !== null) { - return q(this.runningVersion); + return Promise.resolve(this.runningVersion); } return this._getVersion(); } diff --git a/src/vector/rageshake.js b/src/vector/rageshake.js index 07726f6834..d097741471 100644 --- a/src/vector/rageshake.js +++ b/src/vector/rageshake.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import q from "q"; +import Promise from 'bluebird'; // This module contains all the code needed to log the console, persist it to // disk and submit bug reports. Rationale is as follows: @@ -116,7 +116,7 @@ class IndexedDBLogStore { */ connect() { let req = this.indexedDB.open("logs"); - return q.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { req.onsuccess = (event) => { this.db = event.target.result; // Periodically flush logs to local storage / indexeddb @@ -193,7 +193,7 @@ class IndexedDBLogStore { } // there is no flush promise or there was but it has finished, so do // a brand new one, destroying the chain which may have been built up. - this.flushPromise = q.Promise((resolve, reject) => { + this.flushPromise = new Promise((resolve, reject) => { if (!this.db) { // not connected yet or user rejected access for us to r/w to // the db. @@ -277,7 +277,7 @@ class IndexedDBLogStore { } function deleteLogs(id) { - return q.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const txn = db.transaction( ["logs", "logslastmod"], "readwrite" ); @@ -375,7 +375,7 @@ class IndexedDBLogStore { */ function selectQuery(store, keyRange, resultMapper) { const query = store.openCursor(keyRange); - return q.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let results = []; query.onerror = (event) => { reject(new Error("Query failed: " + event.target.errorCode)); diff --git a/src/vector/submit-rageshake.js b/src/vector/submit-rageshake.js index c6c551c61f..b66ec9abe6 100644 --- a/src/vector/submit-rageshake.js +++ b/src/vector/submit-rageshake.js @@ -15,7 +15,7 @@ limitations under the License. */ import pako from 'pako'; -import q from "q"; +import Promise from 'bluebird'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; @@ -100,7 +100,7 @@ export default async function sendBugReport(bugReportEndpoint, opts) { } function _submitReport(endpoint, body, progressCallback) { - const deferred = q.defer(); + const deferred = Promise.defer(); const req = new XMLHttpRequest(); req.open("POST", endpoint); diff --git a/test/app-tests/joining.js b/test/app-tests/joining.js index 11fd3d4810..9e0a84c773 100644 --- a/test/app-tests/joining.js +++ b/test/app-tests/joining.js @@ -33,7 +33,7 @@ var React = require('react'); var ReactDOM = require('react-dom'); var ReactTestUtils = require('react-addons-test-utils'); var expect = require('expect'); -var q = require('q'); +import Promise from 'bluebird'; var test_utils = require('../test-utils'); var MockHttpBackend = require('matrix-mock-request'); @@ -100,29 +100,19 @@ describe('joining a room', function () { // wait for /sync to happen. This may take some time, as the client // has to initialise indexeddb. console.log("waiting for /sync"); - let syncDone = false; httpBackend.when('GET', '/sync') - .check((r) => {syncDone = true;}) .respond(200, {}); - function awaitSync(attempts) { - if (syncDone) { - return q(); - } - if (!attempts) { - throw new Error("Gave up waiting for /sync") - } - return httpBackend.flush().then(() => awaitSync(attempts-1)); - } - return awaitSync(10).then(() => { + return httpBackend.flushAllExpected({ + timeout: 1000, + }).then(() => { // wait for the directory requests httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []}); httpBackend.when('GET', '/thirdparty/protocols').respond(200, {}); - return q.all([ - httpBackend.flush('/thirdparty/protocols'), - httpBackend.flush('/publicRooms'), - ]); + return httpBackend.flushAllExpected(); }).then(() => { + console.log(`${Date.now()} App made requests for directory view; switching to a room.`); + var roomDir = ReactTestUtils.findRenderedComponentWithType( matrixChat, RoomDirectory); @@ -139,19 +129,17 @@ describe('joining a room', function () { httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync") .respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'}); - return q.all([ - httpBackend.flush('/directory/room/'+encodeURIComponent(ROOM_ALIAS), 1, 200), - httpBackend.flush('/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync", 1, 200), - ]); + return httpBackend.flushAllExpected(); }).then(() => { - httpBackend.verifyNoOutstandingExpectation(); + console.log(`${Date.now()} App made room preview request`); - return q.delay(1); - }).then(() => { - // we should now have a roomview, with a preview bar + // we should now have a roomview roomView = ReactTestUtils.findRenderedComponentWithType( matrixChat, RoomView); + // the preview bar may take a tick to be displayed + return Promise.delay(1); + }).then(() => { const previewBar = ReactTestUtils.findRenderedComponentWithType( roomView, RoomPreviewBar); @@ -164,14 +152,14 @@ describe('joining a room', function () { .respond(200, {room_id: ROOM_ID}); }).then(() => { // wait for the join request to be made - return q.delay(1); + return Promise.delay(1); }).then(() => { // and again, because the state update has to go to the store and // then one dispatch within the store, then to the view // XXX: This is *super flaky*: a better way would be to declare // that we expect a certain state transition to happen, then wait // for that transition to occur. - return q.delay(1); + return Promise.delay(1); }).then(() => { // the roomview should now be loading expect(roomView.state.room).toBe(null); @@ -186,7 +174,7 @@ describe('joining a room', function () { }).then(() => { httpBackend.verifyNoOutstandingExpectation(); - return q.delay(1); + return Promise.delay(1); }).then(() => { // We've joined, expect this to false expect(roomView.state.joining).toBe(false); diff --git a/test/app-tests/loading.js b/test/app-tests/loading.js index d01836a36a..c7151aca21 100644 --- a/test/app-tests/loading.js +++ b/test/app-tests/loading.js @@ -22,7 +22,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-addons-test-utils'; import expect from 'expect'; -import q from 'q'; +import Promise from 'bluebird'; +import MatrixReactTestUtils from 'matrix-react-test-utils'; import jssdk from 'matrix-js-sdk'; @@ -103,7 +104,7 @@ describe('loading:', function () { toString: function() { return this.search + this.hash; }, }; - let tokenLoginCompleteDefer = q.defer(); + let tokenLoginCompleteDefer = Promise.defer(); tokenLoginCompletePromise = tokenLoginCompleteDefer.promise; function onNewScreen(screen) { @@ -139,7 +140,7 @@ describe('loading:', function () { realQueryParams={params} startingFragmentQueryParams={fragParts.params} enableGuest={true} - onTokenLoginCompleted={tokenLoginCompleteDefer.resolve} + onTokenLoginCompleted={() => tokenLoginCompleteDefer.resolve()} initialScreenAfterLogin={getScreenFromLocation(windowLocation)} makeRegistrationUrl={() => {throw new Error('Not implemented');}} />, parentDiv @@ -171,7 +172,7 @@ describe('loading:', function () { it('gives a login panel by default', function (done) { loadApp(); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // at this point, we're trying to do a guest registration; // we expect a spinner assertAtLoadingSpinner(matrixChat); @@ -183,11 +184,8 @@ describe('loading:', function () { return httpBackend.flush(); }).then(() => { // Wait for another trip around the event loop for the UI to update - return q.delay(10); + return awaitLoginComponent(matrixChat); }).then(() => { - // we expect a single component following session load - ReactTestUtils.findRenderedComponentWithType( - matrixChat, sdk.getComponent('structures.login.Login')); expect(windowLocation.hash).toEqual("#/login"); }).done(done, done); }); @@ -197,7 +195,7 @@ describe('loading:', function () { uriFragment: "#/room/!room:id", }); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // at this point, we're trying to do a guest registration; // we expect a spinner assertAtLoadingSpinner(matrixChat); @@ -209,7 +207,7 @@ describe('loading:', function () { return httpBackend.flush(); }).then(() => { // Wait for another trip around the event loop for the UI to update - return q.delay(10); + return Promise.delay(10); }).then(() => { return completeLogin(matrixChat); }).then(() => { @@ -232,7 +230,7 @@ describe('loading:', function () { uriFragment: "#/login", }); - return q.delay(100).then(() => { + return awaitLoginComponent(matrixChat).then(() => { // we expect a single component ReactTestUtils.findRenderedComponentWithType( matrixChat, sdk.getComponent('structures.login.Login')); @@ -339,7 +337,7 @@ describe('loading:', function () { }, }); - return q.delay(1).then(() => { + return Promise.delay(1).then(() => { // we expect a loading spinner while we log into the RTS assertAtLoadingSpinner(matrixChat); @@ -366,7 +364,7 @@ describe('loading:', function () { }); // give the UI a chance to display - return q.delay(50); + return awaitLoginComponent(matrixChat); }); it('shows a login view', function() { @@ -403,7 +401,7 @@ describe('loading:', function () { it('shows a home page by default', function (done) { loadApp(); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // at this point, we're trying to do a guest registration; // we expect a spinner assertAtLoadingSpinner(matrixChat); @@ -436,7 +434,7 @@ describe('loading:', function () { loadApp(); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // at this point, we're trying to do a guest registration; // we expect a spinner assertAtLoadingSpinner(matrixChat); @@ -471,7 +469,7 @@ describe('loading:', function () { loadApp({ uriFragment: "#/room/!room:id" }); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // at this point, we're trying to do a guest registration; // we expect a spinner assertAtLoadingSpinner(matrixChat); @@ -530,7 +528,7 @@ describe('loading:', function () { dis.dispatch({ action: 'start_login' }); - return q.delay(1); + return awaitLoginComponent(matrixChat); }); }); @@ -559,7 +557,7 @@ describe('loading:', function () { ReactTestUtils.Simulate.click(returnToApp); - return q.delay(1).then(() => { + return Promise.delay(1).then(() => { // we should be straight back into the home page ReactTestUtils.findRenderedComponentWithType( matrixChat, sdk.getComponent('structures.HomePage')); @@ -574,7 +572,7 @@ describe('loading:', function () { queryString: "?loginToken=secretToken&homeserver=https%3A%2F%2Fhomeserver&identityServer=https%3A%2F%2Fidserver", }); - q.delay(1).then(() => { + Promise.delay(1).then(() => { // we expect a spinner while we're logging in assertAtLoadingSpinner(matrixChat); @@ -607,7 +605,6 @@ describe('loading:', function () { }); }); - // check that we have a Login component, send a 'user:pass' login, // and await the HTTP requests. function completeLogin(matrixChat) { @@ -629,7 +626,7 @@ describe('loading:', function () { return httpBackend.flush().then(() => { // Wait for another trip around the event loop for the UI to update - return q.delay(1); + return Promise.delay(1); }).then(() => { // we expect a spinner ReactTestUtils.findRenderedComponentWithType( @@ -674,7 +671,7 @@ function awaitSyncingSpinner(matrixChat, retryLimit, retryCount) { } // loading can take quite a long time, because we delete the // indexedDB store. - return q.delay(5).then(() => { + return Promise.delay(5).then(() => { return awaitSyncingSpinner(matrixChat, retryLimit, retryCount + 1); }); } @@ -683,7 +680,7 @@ function awaitSyncingSpinner(matrixChat, retryLimit, retryCount) { // state looks good, check the rendered output assertAtSyncingSpinner(matrixChat); - return q(); + return Promise.resolve(); } function assertAtSyncingSpinner(matrixChat) { @@ -711,7 +708,7 @@ function awaitRoomView(matrixChat, retryLimit, retryCount) { throw new Error("MatrixChat still not ready after " + retryCount + " tries"); } - return q.delay(0).then(() => { + return Promise.delay(0).then(() => { return awaitRoomView(matrixChat, retryLimit, retryCount + 1); }); } @@ -721,5 +718,11 @@ function awaitRoomView(matrixChat, retryLimit, retryCount) { // state looks good, check the rendered output ReactTestUtils.findRenderedComponentWithType( matrixChat, sdk.getComponent('structures.RoomView')); - return q(); + return Promise.resolve(); +} + +function awaitLoginComponent(matrixChat, attempts) { + return MatrixReactTestUtils.waitForRenderedComponentWithType( + matrixChat, sdk.getComponent('structures.login.Login'), attempts, + ); } diff --git a/test/test-utils.js b/test/test-utils.js index cda9a017b9..007883df69 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -1,6 +1,6 @@ "use strict"; -var q = require('q'); +import Promise from 'bluebird'; /** * Perform common actions before each test case, e.g. printing the test case @@ -28,7 +28,7 @@ export function browserSupportsWebRTC() { } export function deleteIndexedDB(dbName) { - return new q.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!window.indexedDB) { resolve(); return;