Merge remote-tracking branch 'origin/develop' into erikj/group_server
						commit
						2ccd36d09e
					
				|  | @ -19,4 +19,4 @@ node_js: | |||
| install: | ||||
|     # clone the deps with depth 1: we know we will only ever need that one | ||||
|     # commit. | ||||
|     - scripts/fetch-develop.deps.sh --depth 1 && npm install | ||||
|     - scripts/fetch-develop.deps.sh --depth 1 && npm i phantomjs-prebuilt && npm install | ||||
|  |  | |||
|  | @ -121,6 +121,7 @@ | |||
|     "karma-mocha": "^0.2.2", | ||||
|     "karma-phantomjs-launcher": "^1.0.0", | ||||
|     "karma-webpack": "^1.7.0", | ||||
|     "matrix-mock-request": "^1.0.0", | ||||
|     "minimist": "^1.2.0", | ||||
|     "mkdirp": "^0.5.1", | ||||
|     "mocha": "^2.4.5", | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     onResendClick: function() { | ||||
|         Resend.resend(this.props.mxEvent); | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onViewSourceClick: function() { | ||||
|  | @ -75,7 +75,7 @@ module.exports = React.createClass({ | |||
|         Modal.createDialog(ViewSource, { | ||||
|             content: this.props.mxEvent.event, | ||||
|         }, 'mx_Dialog_viewsource'); | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onViewClearSourceClick: function() { | ||||
|  | @ -84,7 +84,7 @@ module.exports = React.createClass({ | |||
|             // FIXME: _clearEvent is private
 | ||||
|             content: this.props.mxEvent._clearEvent, | ||||
|         }, 'mx_Dialog_viewsource'); | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onRedactClick: function() { | ||||
|  | @ -106,12 +106,12 @@ module.exports = React.createClass({ | |||
|                 }).done(); | ||||
|             }, | ||||
|         }, 'mx_Dialog_confirmredact'); | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onCancelSendClick: function() { | ||||
|         Resend.removeFromQueue(this.props.mxEvent); | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onForwardClick: function() { | ||||
|  | @ -130,7 +130,7 @@ module.exports = React.createClass({ | |||
|         if (this.props.eventTileOps) { | ||||
|             this.props.eventTileOps.unhideWidget(); | ||||
|         } | ||||
|         if (this.props.onFinished) this.props.onFinished(); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     onQuoteClick: function() { | ||||
|  | @ -139,6 +139,7 @@ module.exports = React.createClass({ | |||
|             action: 'quote', | ||||
|             event: this.props.mxEvent, | ||||
|         }); | ||||
|         this.closeMenu(); | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ | |||
| @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"; | ||||
|  |  | |||
|  | @ -14,6 +14,10 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .mx_MEmoteBody { | ||||
|     white-space: pre-wrap; | ||||
| } | ||||
| 
 | ||||
| .mx_MEmoteBody_sender { | ||||
|     cursor: pointer; | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,159 @@ | |||
| /* | ||||
| 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_AppsDrawer { | ||||
| } | ||||
| 
 | ||||
| .mx_AppsContainer { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| .mx_AddWidget_button { | ||||
|     order: 2; | ||||
|     cursor: pointer; | ||||
|     padding-right: 12px; | ||||
|     padding: 0; | ||||
|     margin: 0 0 5px 0; | ||||
|     color: $accent-color; | ||||
|     font-size: 12px; | ||||
| } | ||||
| 
 | ||||
| .mx_SetAppURLDialog_input { | ||||
|     border-radius: 3px; | ||||
|     border: 1px solid $input-border-color; | ||||
|     padding: 9px; | ||||
|     color: $primary-hairline-color; | ||||
|     background-color: $primary-bg-color; | ||||
|     font-size: 15px; | ||||
| } | ||||
| 
 | ||||
| .mx_AppTile { | ||||
|     width: 50%; | ||||
|     margin: 0 5px 2px 0; | ||||
|     border: 1px solid $primary-hairline-color; | ||||
|     border-radius: 2px; | ||||
|     // height: 350px; | ||||
|     // display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .mx_AppTileFullWidth { | ||||
|     width: 100%; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     border: 1px solid $primary-hairline-color; | ||||
|     border-radius: 2px; | ||||
|     // height: 350px; | ||||
|     // display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .mx_AppTileMenuBar { | ||||
|     // height: 15px; | ||||
|     margin: 0; | ||||
|     padding: 2px 10px; | ||||
|     // background-color: $e2e-verified-color; | ||||
|     border-bottom: 1px solid $primary-hairline-color; | ||||
|     font-size: 10px; | ||||
| } | ||||
| 
 | ||||
| .mx_AppTileMenuBarWidgets { | ||||
|     float: right; | ||||
| } | ||||
| .mx_AppTileMenuBarWidget { | ||||
|     // pointer-events: none; | ||||
|     cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .mx_AppTileBody iframe { | ||||
|     width: 100%; | ||||
|     height: 350px; | ||||
|     overflow: hidden; | ||||
|     border: none; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| .mx_CloseAppWidget { | ||||
| } | ||||
| 
 | ||||
| .mx_AppTileMenuBarWidgetPadding { | ||||
|     margin-right: 5px; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile { | ||||
|     background-color: $lightbox-bg-color; | ||||
|     border: 1px solid rgba(0, 0, 0, 0); | ||||
|     width: 200px; | ||||
|     box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); | ||||
|     transition: 0.3s; | ||||
|     border-radius: 3px; | ||||
|     margin: 5px; | ||||
|     display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile.mx_AppIconTile_active { | ||||
|     color: $accent-color; | ||||
|     border-color: $accent-color; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile:hover { | ||||
|     border: 1px solid $accent-color; | ||||
|     box-shadow: 0 0 10px 5px rgba(200,200,200,0.5); | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile_content { | ||||
|     padding: 2px 16px; | ||||
|     height: 60px; | ||||
|     overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile_content h4 { | ||||
|     margin-top: 5px; | ||||
|     margin-bottom: 2px; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile_content p { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 5px; | ||||
|     font-size: smaller; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile_image { | ||||
|     padding: 10px; | ||||
|     width: 75%; | ||||
|     max-width:100px; | ||||
|     max-height:100px; | ||||
|     width: auto; | ||||
|     height: auto; | ||||
| } | ||||
| 
 | ||||
| .mx_AppIconTile_imageContainer { | ||||
|     text-align: center; | ||||
|     width: 100%; | ||||
|     background-color: white; | ||||
|     border-radius: 3px 3px 0 0; | ||||
|     height: 155px; | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| form.mx_Custom_Widget_Form div { | ||||
|     margin-top: 10px; | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | @ -38,6 +38,7 @@ | |||
| .mx_Autocomplete_Completion_pill { | ||||
|     border-radius: 17px; | ||||
|     height: 34px; | ||||
|     padding: 0px 5px; | ||||
|     display: flex; | ||||
|     user-select: none; | ||||
|     cursor: pointer; | ||||
|  | @ -45,10 +46,21 @@ | |||
|     color: $primary-fg-color; | ||||
| } | ||||
| 
 | ||||
| .mx_Autocomplete_Completion_pill * { | ||||
| .mx_Autocomplete_Completion_pill > * { | ||||
|     margin: 0 3px; | ||||
| } | ||||
| 
 | ||||
| .mx_Autocomplete_provider_name, | ||||
| .mx_Autocomplete_Completion_title, | ||||
| .mx_Autocomplete_Completion_subtitle, | ||||
| .mx_Autocomplete_Completion_description { | ||||
|     /* Ellipsis for long names/subtitles/descriptions*/ | ||||
|     max-width: 150px; | ||||
|     white-space: nowrap; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| /* container for pill-style completions */ | ||||
| .mx_Autocomplete_Completion_container_pill { | ||||
|     margin: 12px; | ||||
|  |  | |||
|  | @ -128,7 +128,8 @@ limitations under the License. | |||
| .mx_MessageComposer_upload, | ||||
| .mx_MessageComposer_hangup, | ||||
| .mx_MessageComposer_voicecall, | ||||
| .mx_MessageComposer_videocall { | ||||
| .mx_MessageComposer_videocall, | ||||
| .mx_MessageComposer_apps { | ||||
|     /*display: table-cell;*/ | ||||
|     /*vertical-align: middle;*/ | ||||
|     /*padding-left: 10px;*/ | ||||
|  | @ -140,7 +141,8 @@ limitations under the License. | |||
| .mx_MessageComposer_upload object, | ||||
| .mx_MessageComposer_hangup object, | ||||
| .mx_MessageComposer_voicecall object, | ||||
| .mx_MessageComposer_videocall object { | ||||
| .mx_MessageComposer_videocall object, | ||||
| .mx_MessageComposer_apps object { | ||||
|     pointer-events: none; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> | ||||
| <g> | ||||
| 	 | ||||
| 		<rect x="178.846" y="92.087" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 224.3476 631.1498)" width="128.085" height="354.049"/> | ||||
| 	<path d="M471.723,88.393l-48.115-48.114c-11.723-11.724-31.558-10.896-44.304,1.85l-45.202,45.203l90.569,90.568l45.202-45.202 | ||||
| 		C482.616,119.952,483.445,100.116,471.723,88.393z"/> | ||||
| 	<polygon points="64.021,363.252 32,480 148.737,447.979 	"/> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 876 B | 
|  | @ -0,0 +1,24 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="35px" | ||||
| 	 height="35px" viewBox="0 0 35 35" enable-background="new 0 0 35 35" xml:space="preserve"> | ||||
| <g id="Layer_1"> | ||||
| 	<path id="Oval-109-Copy" fill="#76CFA6" enable-background="new    " d="M17.5,35C27.165,35,35,27.165,35,17.5S27.165,0,17.5,0 | ||||
| 		S0,7.835,0,17.5S7.835,35,17.5,35z"/> | ||||
| 	<g id="Icon"> | ||||
| 		<g> | ||||
| 			<path fill="none" stroke="#FFFFFF" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5 | ||||
| 				V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/> | ||||
| 		</g> | ||||
| 	</g> | ||||
| </g> | ||||
| <g id="Layer_2"> | ||||
| 	<g id="Icon_1_" opacity="0.15"> | ||||
| 		<g> | ||||
| 			<path fill="none" stroke="#76CFA6" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5 | ||||
| 				V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/> | ||||
| 		</g> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.2 KiB | 
|  | @ -0,0 +1,14 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 width="35px" height="35px" viewBox="0 0 35 35" enable-background="new 0 0 35 35" xml:space="preserve"> | ||||
| <path id="Oval-109-Copy" opacity="0.15" fill="#76CFA6" enable-background="new    " d="M17.5,35C27.165,35,35,27.165,35,17.5 | ||||
| 	S27.165,0,17.5,0S0,7.835,0,17.5S7.835,35,17.5,35z"/> | ||||
| <g id="Icon"> | ||||
| 	<g> | ||||
| 		<path fill="none" stroke="#76CFA6" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5 | ||||
| 			V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 941 B | 
|  | @ -36,7 +36,7 @@ var expect = require('expect'); | |||
| var q = require('q'); | ||||
| 
 | ||||
| var test_utils = require('../test-utils'); | ||||
| var MockHttpBackend = require('../mock-request'); | ||||
| var MockHttpBackend = require('matrix-mock-request'); | ||||
| 
 | ||||
| var HS_URL='http://localhost'; | ||||
| var IS_URL='http://localhost'; | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ import {VIEWS} from 'matrix-react-sdk/lib/components/structures/MatrixChat'; | |||
| import dis from 'matrix-react-sdk/lib/dispatcher'; | ||||
| 
 | ||||
| import * as test_utils from '../test-utils'; | ||||
| import MockHttpBackend from '../mock-request'; | ||||
| import MockHttpBackend from 'matrix-mock-request'; | ||||
| import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils'; | ||||
| 
 | ||||
| var DEFAULT_HS_URL='http://my_server'; | ||||
|  |  | |||
|  | @ -1,336 +0,0 @@ | |||
| "use strict"; | ||||
| const q = require("q"); | ||||
| import expect from 'expect'; | ||||
| 
 | ||||
| /** | ||||
|  * Construct a mock HTTP backend, heavily inspired by Angular.js. | ||||
|  * @constructor | ||||
|  */ | ||||
| function HttpBackend() { | ||||
|     this.requests = []; | ||||
|     this.expectedRequests = []; | ||||
|     const self = this; | ||||
|     // the request function dependency that the SDK needs.
 | ||||
|     this.requestFn = function(opts, callback) { | ||||
|         const req = new Request(opts, callback); | ||||
|         console.log(`${Date.now()} HTTP backend received request: ${req}`); | ||||
|         self.requests.push(req); | ||||
| 
 | ||||
|         const abort = function() { | ||||
|             const idx = self.requests.indexOf(req); | ||||
|             if (idx >= 0) { | ||||
|                 console.log("Aborting HTTP request: %s %s", opts.method, | ||||
|                             opts.uri); | ||||
|                 self.requests.splice(idx, 1); | ||||
|                 req.callback("aborted"); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         return { | ||||
|             abort: abort, | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     // very simplistic mapping from the whatwg fetch interface onto the request
 | ||||
|     // interface, so we can use the same mock backend for both.
 | ||||
|     this.fetchFn = function(input, init) { | ||||
|         init = init || {}; | ||||
|         const requestOpts = { | ||||
|             uri: input, | ||||
|             method: init.method || 'GET', | ||||
|             body: init.body, | ||||
|         }; | ||||
| 
 | ||||
|         return new Promise((resolve, reject) => { | ||||
|             function callback(err, response, body) { | ||||
|                 if (err) { | ||||
|                     reject(err); | ||||
|                 } | ||||
|                 resolve({ | ||||
|                     ok: response.statusCode >= 200 && response.statusCode < 300, | ||||
|                     json: () => body, | ||||
|                 }); | ||||
|             }; | ||||
| 
 | ||||
|             const req = new Request(requestOpts, callback); | ||||
|             console.log(`HTTP backend received request: ${req}`); | ||||
|             self.requests.push(req); | ||||
|         }); | ||||
|     }; | ||||
| } | ||||
| HttpBackend.prototype = { | ||||
|     /** | ||||
|      * Respond to all of the requests (flush the queue). | ||||
|      * @param {string} path The path to flush (optional) default: all. | ||||
|      * @param {integer} numToFlush The number of things to flush (optional), default: all. | ||||
|      * @param {integer=} waitTime  The time (in ms) to wait for a request to happen. | ||||
|      *    default: 100 | ||||
|      * | ||||
|      * @return {Promise} resolves when there is nothing left to flush, with the | ||||
|      *    number of requests flushed | ||||
|      */ | ||||
|     flush: function(path, numToFlush, waitTime) { | ||||
|         const defer = q.defer(); | ||||
|         const self = this; | ||||
|         let flushed = 0; | ||||
|         if (waitTime === undefined) { | ||||
|             waitTime = 100; | ||||
|         } | ||||
| 
 | ||||
|         function log(msg) { | ||||
|             console.log(`${Date.now()} flush[${path || ''}]: ${msg}`); | ||||
|         } | ||||
| 
 | ||||
|         log("HTTP backend flushing... (path=" + path | ||||
|             + " numToFlush=" + numToFlush | ||||
|             + " waitTime=" + waitTime | ||||
|             + ")", | ||||
|         ); | ||||
|         const endTime =  waitTime + Date.now(); | ||||
| 
 | ||||
|         const tryFlush = function() { | ||||
|             // if there's more real requests and more expected requests, flush 'em.
 | ||||
|             log(`  trying to flush => reqs=[${self.requests}] ` + | ||||
|                 `expected=[${self.expectedRequests}]`, | ||||
|             ); | ||||
|             if (self._takeFromQueue(path)) { | ||||
|                 // try again on the next tick.
 | ||||
|                 flushed += 1; | ||||
|                 if (numToFlush && flushed === numToFlush) { | ||||
|                     log(`Flushed assigned amount: ${numToFlush}`); | ||||
|                     defer.resolve(flushed); | ||||
|                 } else { | ||||
|                     log(`  flushed. Trying for more.`); | ||||
|                     setTimeout(tryFlush, 0); | ||||
|                 } | ||||
|             } else if (flushed === 0 && Date.now() < endTime) { | ||||
|                 // we may not have made the request yet, wait a generous amount of
 | ||||
|                 // time before giving up.
 | ||||
|                 log(`  nothing to flush yet; waiting for requests.`); | ||||
|                 setTimeout(tryFlush, 5); | ||||
|             } else { | ||||
|                 if (flushed === 0) { | ||||
|                     log("nothing to flush; giving up"); | ||||
|                 } else { | ||||
|                     log(`no more flushes after flushing ${flushed} requests`); | ||||
|                 } | ||||
|                 defer.resolve(flushed); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         setTimeout(tryFlush, 0); | ||||
| 
 | ||||
|         return defer.promise; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Attempts to resolve requests/expected requests. | ||||
|      * @param {string} path The path to flush (optional) default: all. | ||||
|      * @return {boolean} true if something was resolved. | ||||
|      */ | ||||
|     _takeFromQueue: function(path) { | ||||
|         let req = null; | ||||
|         let i; | ||||
|         let j; | ||||
|         let matchingReq = null; | ||||
|         let expectedReq = null; | ||||
|         let testResponse = null; | ||||
|         for (i = 0; i < this.requests.length; i++) { | ||||
|             req = this.requests[i]; | ||||
|             for (j = 0; j < this.expectedRequests.length; j++) { | ||||
|                 expectedReq = this.expectedRequests[j]; | ||||
|                 if (path && path !== expectedReq.path) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (expectedReq.method === req.method && | ||||
|                         req.path.indexOf(expectedReq.path) !== -1) { | ||||
|                     if (!expectedReq.data || (JSON.stringify(expectedReq.data) === | ||||
|                             JSON.stringify(req.data))) { | ||||
|                         matchingReq = expectedReq; | ||||
|                         this.expectedRequests.splice(j, 1); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (matchingReq) { | ||||
|                 // remove from request queue
 | ||||
|                 this.requests.splice(i, 1); | ||||
|                 i--; | ||||
| 
 | ||||
|                 for (j = 0; j < matchingReq.checks.length; j++) { | ||||
|                     matchingReq.checks[j](req); | ||||
|                 } | ||||
|                 testResponse = matchingReq.response; | ||||
|                 console.log(`${Date.now()}    responding to ${matchingReq.path}`); | ||||
|                 let body = testResponse.body; | ||||
|                 if (Object.prototype.toString.call(body) == "[object Function]") { | ||||
|                     body = body(req.path, req.data); | ||||
|                 } | ||||
|                 req.callback( | ||||
|                     testResponse.err, testResponse.response, body, | ||||
|                 ); | ||||
|                 matchingReq = null; | ||||
|             } | ||||
|         } | ||||
|         if (testResponse) {  // flushed something
 | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Makes sure that the SDK hasn't sent any more requests to the backend. | ||||
|      */ | ||||
|     verifyNoOutstandingRequests: function() { | ||||
|         const firstOutstandingReq = this.requests[0] || {}; | ||||
|         expect(this.requests.length).toEqual(0, | ||||
|             "Expected no more HTTP requests but received request to " + | ||||
|             firstOutstandingReq.path, | ||||
|         ); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Makes sure that the test doesn't have any unresolved requests. | ||||
|      */ | ||||
|     verifyNoOutstandingExpectation: function() { | ||||
|         const firstOutstandingExpectation = this.expectedRequests[0] || {}; | ||||
|         expect(this.expectedRequests.length).toEqual(0, | ||||
|             "Expected to see HTTP request for " + firstOutstandingExpectation.path, | ||||
|         ); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Create an expected request. | ||||
|      * @param {string} method The HTTP method | ||||
|      * @param {string} path The path (which can be partial) | ||||
|      * @param {Object} data The expected data. | ||||
|      * @return {Request} An expected request. | ||||
|      */ | ||||
|     when: function(method, path, data) { | ||||
|         const pendingReq = new ExpectedRequest(method, path, data); | ||||
|         this.expectedRequests.push(pendingReq); | ||||
|         return pendingReq; | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Represents the expectation of a request. | ||||
|  * | ||||
|  * <p>Includes the conditions to be matched against, the checks to be made, | ||||
|  * and the response to be returned. | ||||
|  * | ||||
|  * @constructor | ||||
|  * @param {string} method | ||||
|  * @param {string} path | ||||
|  * @param {object?} data | ||||
|  */ | ||||
| function ExpectedRequest(method, path, data) { | ||||
|     this.method = method; | ||||
|     this.path = path; | ||||
|     this.data = data; | ||||
|     this.response = null; | ||||
|     this.checks = []; | ||||
| } | ||||
| 
 | ||||
| ExpectedRequest.prototype = { | ||||
|     toString: function() { | ||||
|         return this.method + " " + this.path | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Execute a check when this request has been satisfied. | ||||
|      * @param {Function} fn The function to execute. | ||||
|      * @return {Request} for chaining calls. | ||||
|      */ | ||||
|     check: function(fn) { | ||||
|         this.checks.push(fn); | ||||
|         return this; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Respond with the given data when this request is satisfied. | ||||
|      * @param {Number} code The HTTP status code. | ||||
|      * @param {Object|Function} data The HTTP JSON body. If this is a function, | ||||
|      * it will be invoked when the JSON body is required (which should be returned). | ||||
|      */ | ||||
|     respond: function(code, data) { | ||||
|         this.response = { | ||||
|             response: { | ||||
|                 statusCode: code, | ||||
|                 headers: {}, | ||||
|             }, | ||||
|             body: data, | ||||
|             err: null, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Fail with an Error when this request is satisfied. | ||||
|      * @param {Number} code The HTTP status code. | ||||
|      * @param {Error} err The error to throw (e.g. Network Error) | ||||
|      */ | ||||
|     fail: function(code, err) { | ||||
|         this.response = { | ||||
|             response: { | ||||
|                 statusCode: code, | ||||
|                 headers: {}, | ||||
|             }, | ||||
|             body: null, | ||||
|             err: err, | ||||
|         }; | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Represents a request made by the app. | ||||
|  * | ||||
|  * @constructor | ||||
|  * @param {object} opts opts passed to request() | ||||
|  * @param {function} callback | ||||
|  */ | ||||
| function Request(opts, callback) { | ||||
|     this.opts = opts; | ||||
|     this.callback = callback; | ||||
| 
 | ||||
|     Object.defineProperty(this, 'method', { | ||||
|         get: function() { | ||||
|             return opts.method; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(this, 'path', { | ||||
|         get: function() { | ||||
|             return opts.uri; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(this, 'data', { | ||||
|         get: function() { | ||||
|             return opts.body; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(this, 'queryParams', { | ||||
|         get: function() { | ||||
|             return opts.qs; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(this, 'headers', { | ||||
|         get: function() { | ||||
|             return opts.headers || {}; | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| Request.prototype = { | ||||
|     toString: function() { | ||||
|         return this.method + " " + this.path; | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * The HttpBackend class. | ||||
|  */ | ||||
| module.exports = HttpBackend; | ||||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker