mirror of https://github.com/vector-im/riot-web
				
				
				
			Merge remote-tracking branch 'origin/experimental' into travis/fix-memberlist-order
						commit
						cc8fa7911b
					
				|  | @ -30,7 +30,7 @@ popd | |||
| if [ "$TRAVIS_BRANCH" = "develop" ] | ||||
| then | ||||
|     # run end to end tests | ||||
|     git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master | ||||
|     scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master | ||||
|     pushd matrix-react-end-to-end-tests | ||||
|     ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web | ||||
|     # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh | ||||
|  |  | |||
|  | @ -124,8 +124,9 @@ | |||
|     "eslint-plugin-flowtype": "^2.30.0", | ||||
|     "eslint-plugin-react": "^7.7.0", | ||||
|     "estree-walker": "^0.5.0", | ||||
|     "expect": "^1.16.0", | ||||
|     "expect": "^23.6.0", | ||||
|     "flow-parser": "^0.57.3", | ||||
|     "jest-mock": "^23.2.0", | ||||
|     "karma": "^3.0.0", | ||||
|     "karma-chrome-launcher": "^0.2.3", | ||||
|     "karma-cli": "^1.0.1", | ||||
|  |  | |||
|  | @ -25,8 +25,10 @@ | |||
| @import "./structures/_ViewSource.scss"; | ||||
| @import "./structures/login/_Login.scss"; | ||||
| @import "./views/avatars/_BaseAvatar.scss"; | ||||
| @import "./views/avatars/_MemberStatusMessageAvatar.scss"; | ||||
| @import "./views/context_menus/_MessageContextMenu.scss"; | ||||
| @import "./views/context_menus/_RoomTileContextMenu.scss"; | ||||
| @import "./views/context_menus/_StatusMessageContextMenu.scss"; | ||||
| @import "./views/context_menus/_TagTileContextMenu.scss"; | ||||
| @import "./views/context_menus/_TopLeftMenu.scss"; | ||||
| @import "./views/dialogs/_BugReportDialog.scss"; | ||||
|  | @ -35,7 +37,6 @@ | |||
| @import "./views/dialogs/_ChatInviteDialog.scss"; | ||||
| @import "./views/dialogs/_ConfirmUserActionDialog.scss"; | ||||
| @import "./views/dialogs/_CreateGroupDialog.scss"; | ||||
| @import "./views/dialogs/_CreateKeyBackupDialog.scss"; | ||||
| @import "./views/dialogs/_CreateRoomDialog.scss"; | ||||
| @import "./views/dialogs/_DeactivateAccountDialog.scss"; | ||||
| @import "./views/dialogs/_DevtoolsDialog.scss"; | ||||
|  |  | |||
|  | @ -14,12 +14,8 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog { | ||||
|     padding-right: 40px; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_recoveryKey { | ||||
|     padding: 20px; | ||||
|     color: $info-plinth-fg-color; | ||||
|     background-color: $info-plinth-bg-color; | ||||
| .mx_MemberStatusMessageAvatar_hasStatus { | ||||
|     border: 2px solid $accent-color; | ||||
|     border-radius: 40px; | ||||
|     padding-right: 0 !important; /* Override AccessibleButton styling */ | ||||
| } | ||||
|  | @ -0,0 +1,55 @@ | |||
| /* | ||||
| Copyright 2018 New Vector 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_StatusMessageContextMenu_message { | ||||
|     display: inline-block; | ||||
|     border-radius: 3px 0 0 3px; | ||||
|     border: 1px solid $input-border-color; | ||||
|     font-size: 13px; | ||||
|     padding: 7px 7px 7px 9px; | ||||
|     width: 135px; | ||||
|     background-color: $primary-bg-color !important; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_submit { | ||||
|     display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_submitFaded { | ||||
|     opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_submit img { | ||||
|     vertical-align: middle; | ||||
|     margin-left: 8px; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu hr { | ||||
|     border: 0.5px solid $menu-border-color; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_clearIcon { | ||||
|     margin: 5px 15px 5px 5px; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_clear { | ||||
|     padding: 2px; | ||||
| } | ||||
| 
 | ||||
| .mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear { | ||||
|     color: $warning-color; | ||||
| } | ||||
|  | @ -13,7 +13,11 @@ 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_CreateKeyBackupDialog { | ||||
|     padding-right: 40px; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_primaryContainer { | ||||
|     /*FIXME: plinth colour in new theme(s). background-color: $accent-color;*/ | ||||
|     padding: 20px | ||||
|  | @ -25,9 +29,13 @@ limitations under the License. | |||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_passPhraseContainer { | ||||
|     display: flex; | ||||
|     align-items: start; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_passPhraseHelp { | ||||
|     float: right; | ||||
|     width: 230px; | ||||
|     flex: 1; | ||||
|     height: 85px; | ||||
|     margin-left: 20px; | ||||
|     font-size: 80%; | ||||
|  | @ -38,20 +46,36 @@ limitations under the License. | |||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_passPhraseInput { | ||||
|     flex: none; | ||||
|     width: 250px; | ||||
|     border: 1px solid $accent-color; | ||||
|     border-radius: 5px; | ||||
|     padding: 10px; | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_passPhraseMatch { | ||||
|     float: right; | ||||
|     margin-left: 20px; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_recoveryKeyButtons { | ||||
|     float: right; | ||||
| .mx_CreateKeyBackupDialog_recoveryKeyHeader { | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_recoveryKeyContainer { | ||||
|     display: flex; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_recoveryKey { | ||||
|     width: 300px; | ||||
|     width: 262px; | ||||
|     padding: 20px; | ||||
|     color: $info-plinth-fg-color; | ||||
|     background-color: $info-plinth-bg-color; | ||||
|     margin-right: 12px; | ||||
| } | ||||
| 
 | ||||
| .mx_CreateKeyBackupDialog_recoveryKeyButtons { | ||||
|     flex: 1; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
| } | ||||
|  |  | |||
|  | @ -107,3 +107,10 @@ limitations under the License. | |||
| } | ||||
| */ | ||||
| 
 | ||||
| .mx_EntityTile_subtext { | ||||
|     font-size: 11px; | ||||
|     opacity: 0.5; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|     text-overflow: clip; | ||||
| } | ||||
|  |  | |||
|  | @ -132,6 +132,13 @@ limitations under the License. | |||
|     margin-left: 8px; | ||||
| } | ||||
| 
 | ||||
| .mx_MemberInfo_statusMessage { | ||||
|     font-size: 11px; | ||||
|     opacity: 0.5; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|     text-overflow: clip; | ||||
| } | ||||
| .mx_MemberInfo .mx_MemberInfo_scrollContainer { | ||||
|     flex: 1; | ||||
| } | ||||
|  |  | |||
|  | @ -48,15 +48,48 @@ limitations under the License. | |||
|     left: -12px; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_avatar { | ||||
|     flex: 0; | ||||
|     padding: 4px; | ||||
| 
 | ||||
| .mx_RoomTile_nameContainer { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     flex: 1; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_labelContainer { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     flex: 1; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_subtext { | ||||
|     display: inline-block; | ||||
|     font-size: 11px; | ||||
|     padding: 0 0 0 7px; | ||||
|     margin: 0; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|     text-overflow: clip; | ||||
|     position: relative; | ||||
|     bottom: 4px; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_avatar_container { | ||||
|     position: relative; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_avatar { | ||||
|     flex: 0; | ||||
|     padding: 4px; | ||||
|     width: 24px; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_hasSubtext .mx_RoomTile_avatar { | ||||
|     padding-top: 0; | ||||
|     vertical-align: super; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomTile_dm { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|  | @ -69,7 +102,7 @@ limitations under the License. | |||
|     flex: 1 5 auto; | ||||
|     font-size: 14px; | ||||
|     font-weight: 600; | ||||
|     padding: 6px; | ||||
|     padding: 0 6px; | ||||
|     color: $roomtile-name-color; | ||||
|     white-space: nowrap; | ||||
|     overflow-x: hidden; | ||||
|  |  | |||
|  | @ -0,0 +1,17 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||||
|     <!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch --> | ||||
|     <title>Tick</title> | ||||
|     <desc>Created with Sketch.</desc> | ||||
|     <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||||
|         <g id="Custom-Status-Copy" transform="translate(-529.000000, -917.000000)" fill-rule="nonzero"> | ||||
|             <g id="Tick" transform="translate(530.000000, 918.000000)"> | ||||
|                 <circle id="Oval" stroke="#6AAC8C" fill="#75CFA6" cx="9" cy="9" r="9"></circle> | ||||
|                 <g id="Glyph" transform="translate(8.949747, 7.949747) rotate(-45.000000) translate(-8.949747, -7.949747) translate(4.449747, 5.449747)" fill="#FFFFFF"> | ||||
|                     <rect id="Rectangle" x="0" y="0" width="2" height="5"></rect> | ||||
|                     <rect id="Rectangle" x="0" y="3" width="9" height="2"></rect> | ||||
|                 </g> | ||||
|             </g> | ||||
|         </g> | ||||
|     </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
|  | @ -2,6 +2,9 @@ | |||
| 
 | ||||
| org="$1" | ||||
| repo="$2" | ||||
| defbranch="$3" | ||||
| 
 | ||||
| [ -z "$defbranch" ] && defbranch="develop" | ||||
| 
 | ||||
| rm -r "$repo" || true | ||||
| 
 | ||||
|  | @ -20,5 +23,5 @@ clone $TRAVIS_PULL_REQUEST_BRANCH | |||
| clone $TRAVIS_BRANCH | ||||
| # Try the current branch from Jenkins. | ||||
| clone `"echo $GIT_BRANCH" | sed -e 's/^origin\///'` | ||||
| # Use develop as the last resort. | ||||
| clone develop | ||||
| # Use the default branch as the last resort. | ||||
| clone $defbranch | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/Login.js
								
								
								
								
							
							
						
						
									
										13
									
								
								src/Login.js
								
								
								
								
							|  | @ -204,6 +204,19 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { | |||
| 
 | ||||
|     const data = await client.login(loginType, loginParams); | ||||
| 
 | ||||
|     const wellknown = data.well_known; | ||||
|     if (wellknown) { | ||||
|         if (wellknown["m.homeserver"] && wellknown["m.homeserver"]["base_url"]) { | ||||
|             hsUrl = wellknown["m.homeserver"]["base_url"]; | ||||
|             console.log(`Overrode homeserver setting with ${hsUrl} from login response`); | ||||
|         } | ||||
|         if (wellknown["m.identity_server"] && wellknown["m.identity_server"]["base_url"]) { | ||||
|             // TODO: should we prompt here?
 | ||||
|             isUrl = wellknown["m.identity_server"]["base_url"]; | ||||
|             console.log(`Overrode IS setting with ${isUrl} from login response`); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         homeserverUrl: hsUrl, | ||||
|         identityServerUrl: isUrl, | ||||
|  |  | |||
|  | @ -289,11 +289,6 @@ const Notifier = { | |||
|         const room = MatrixClientPeg.get().getRoom(ev.getRoomId()); | ||||
|         const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); | ||||
|         if (actions && actions.notify) { | ||||
|             dis.dispatch({ | ||||
|                 action: "event_notification", | ||||
|                 event: ev, | ||||
|                 room: room, | ||||
|             }); | ||||
|             if (this.isEnabled()) { | ||||
|                 this._displayPopupNotification(ev, room); | ||||
|             } | ||||
|  |  | |||
|  | @ -392,7 +392,7 @@ class Tinter { | |||
|     // XXX: we could just move this all into TintableSvg, but as it's so similar
 | ||||
|     // to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
 | ||||
|     // keeping it here for now.
 | ||||
|     calcSvgFixups(svgs, forceColors) { | ||||
|     calcSvgFixups(svgs) { | ||||
|         // go through manually fixing up SVG colours.
 | ||||
|         // we could do this by stylesheets, but keeping the stylesheets
 | ||||
|         // updated would be a PITA, so just brute-force search for the
 | ||||
|  | @ -420,21 +420,13 @@ class Tinter { | |||
|                 const tag = tags[j]; | ||||
|                 for (let k = 0; k < this.svgAttrs.length; k++) { | ||||
|                     const attr = this.svgAttrs[k]; | ||||
|                     for (let m = 0; m < this.keyHex.length; m++) { // dev note: don't use L please.
 | ||||
|                         // We use a different attribute from the one we're setting
 | ||||
|                         // because we may also be using forceColors. If we were to
 | ||||
|                         // check the keyHex against a forceColors value, it may not
 | ||||
|                         // match and therefore not change when we need it to.
 | ||||
|                         const valAttrName = "mx-val-" + attr; | ||||
|                         let attribute = tag.getAttribute(valAttrName); | ||||
|                         if (!attribute) attribute = tag.getAttribute(attr); // fall back to the original
 | ||||
|                         if (attribute && (attribute.toUpperCase() === this.keyHex[m] || attribute.toLowerCase() === this.keyRgb[m])) { | ||||
|                     for (let l = 0; l < this.keyHex.length; l++) { | ||||
|                         if (tag.getAttribute(attr) && | ||||
|                             tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) { | ||||
|                             fixups.push({ | ||||
|                                 node: tag, | ||||
|                                 attr: attr, | ||||
|                                 refAttr: valAttrName, | ||||
|                                 index: m, | ||||
|                                 forceColors: forceColors, | ||||
|                                 index: l, | ||||
|                             }); | ||||
|                         } | ||||
|                     } | ||||
|  | @ -450,9 +442,7 @@ class Tinter { | |||
|         if (DEBUG) console.log("applySvgFixups start for " + fixups); | ||||
|         for (let i = 0; i < fixups.length; i++) { | ||||
|             const svgFixup = fixups[i]; | ||||
|             const forcedColor = svgFixup.forceColors ? svgFixup.forceColors[svgFixup.index] : null; | ||||
|             svgFixup.node.setAttribute(svgFixup.attr, forcedColor ? forcedColor : this.colors[svgFixup.index]); | ||||
|             svgFixup.node.setAttribute(svgFixup.refAttr, this.colors[svgFixup.index]); | ||||
|             svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]); | ||||
|         } | ||||
|         if (DEBUG) console.log("applySvgFixups end"); | ||||
|     } | ||||
|  |  | |||
|  | @ -239,17 +239,19 @@ export default React.createClass({ | |||
|             <p>{_t("You'll need it if you log out or lose access to this device.")}</p> | ||||
| 
 | ||||
|             <div className="mx_CreateKeyBackupDialog_primaryContainer"> | ||||
|                 <div className="mx_CreateKeyBackupDialog_passPhraseHelp"> | ||||
|                     {strengthMeter} | ||||
|                     {helpText} | ||||
|                 <div className="mx_CreateKeyBackupDialog_passPhraseContainer"> | ||||
|                     <input type="password" | ||||
|                         onChange={this._onPassPhraseChange} | ||||
|                         onKeyPress={this._onPassPhraseKeyPress} | ||||
|                         value={this.state.passPhrase} | ||||
|                         className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                         placeholder={_t("Enter a passphrase...")} | ||||
|                     /> | ||||
|                     <div className="mx_CreateKeyBackupDialog_passPhraseHelp"> | ||||
|                         {strengthMeter} | ||||
|                         {helpText} | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <input type="password" | ||||
|                     onChange={this._onPassPhraseChange} | ||||
|                     onKeyPress={this._onPassPhraseKeyPress} | ||||
|                     value={this.state.passPhrase} | ||||
|                     className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                     placeholder={_t("Enter a passphrase...")} | ||||
|                 /> | ||||
|             </div> | ||||
| 
 | ||||
|             <DialogButtons primaryButton={_t('Next')} | ||||
|  | @ -317,16 +319,18 @@ export default React.createClass({ | |||
|                 "somewhere safe.", | ||||
|             )}</p> | ||||
|             <div className="mx_CreateKeyBackupDialog_primaryContainer"> | ||||
|                 {passPhraseMatch} | ||||
|                 <div> | ||||
|                     <input type="password" | ||||
|                         onChange={this._onPassPhraseConfirmChange} | ||||
|                         onKeyPress={this._onPassPhraseConfirmKeyPress} | ||||
|                         value={this.state.passPhraseConfirm} | ||||
|                         className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                         placeholder={_t("Repeat your passphrase...")} | ||||
|                         autoFocus={true} | ||||
|                     /> | ||||
|                 <div className="mx_CreateKeyBackupDialog_passPhraseContainer"> | ||||
|                     <div> | ||||
|                         <input type="password" | ||||
|                             onChange={this._onPassPhraseConfirmChange} | ||||
|                             onKeyPress={this._onPassPhraseConfirmKeyPress} | ||||
|                             value={this.state.passPhraseConfirm} | ||||
|                             className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                             placeholder={_t("Repeat your passphrase...")} | ||||
|                             autoFocus={true} | ||||
|                         /> | ||||
|                     </div> | ||||
|                     {passPhraseMatch} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <DialogButtons primaryButton={_t('Next')} | ||||
|  | @ -351,21 +355,21 @@ export default React.createClass({ | |||
|             <p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p> | ||||
|             <p>{bodyText}</p> | ||||
|             <p className="mx_CreateKeyBackupDialog_primaryContainer"> | ||||
|                 <div>{_t("Your Recovery Key")}</div> | ||||
|                 <div className="mx_CreateKeyBackupDialog_recoveryKeyButtons"> | ||||
|                     <button onClick={this._onCopyClick}> | ||||
|                         {_t("Copy to clipboard")} | ||||
|                     </button> | ||||
|                     { | ||||
|                         // FIXME REDESIGN: buttons should be adjacent but insufficient room in current design
 | ||||
|                     } | ||||
|                     <br /><br /> | ||||
|                     <button onClick={this._onDownloadClick}> | ||||
|                         {_t("Download")} | ||||
|                     </button> | ||||
|                 <div className="mx_CreateKeyBackupDialog_recoveryKeyHeader"> | ||||
|                     {_t("Your Recovery Key")} | ||||
|                 </div> | ||||
|                 <div className="mx_CreateKeyBackupDialog_recoveryKey"> | ||||
|                     <code ref={this._collectRecoveryKeyNode}>{this._keyBackupInfo.recovery_key}</code> | ||||
|                 <div className="mx_CreateKeyBackupDialog_recoveryKeyContainer"> | ||||
|                     <div className="mx_CreateKeyBackupDialog_recoveryKey"> | ||||
|                         <code ref={this._collectRecoveryKeyNode}>{this._keyBackupInfo.recovery_key}</code> | ||||
|                     </div> | ||||
|                     <div className="mx_CreateKeyBackupDialog_recoveryKeyButtons"> | ||||
|                         <button className="mx_Dialog_primary" onClick={this._onCopyClick}> | ||||
|                             {_t("Copy to clipboard")} | ||||
|                         </button> | ||||
|                         <button className="mx_Dialog_primary" onClick={this._onDownloadClick}> | ||||
|                             {_t("Download")} | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </p> | ||||
|             <br /> | ||||
|  |  | |||
|  | @ -91,11 +91,15 @@ class HomePage extends React.Component { | |||
|         this._unmounted = true; | ||||
|     } | ||||
| 
 | ||||
|     onLoginClick() { | ||||
|     onLoginClick(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         dis.dispatch({ action: 'start_login' }); | ||||
|     } | ||||
| 
 | ||||
|     onRegisterClick() { | ||||
|     onRegisterClick(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         dis.dispatch({ action: 'start_registration' }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -927,6 +927,10 @@ export default React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     _viewHome: function() { | ||||
|         // The home page requires the "logged in" view, so we'll set that.
 | ||||
|         this.setStateForNewView({ | ||||
|             view: VIEWS.LOGGED_IN, | ||||
|         }); | ||||
|         this._setPage(PageTypes.HomePage); | ||||
|         this.notifyNewScreen('home'); | ||||
|     }, | ||||
|  | @ -1183,10 +1187,7 @@ export default React.createClass({ | |||
|      * @param {string} teamToken | ||||
|      */ | ||||
|     _onLoggedIn: async function(teamToken) { | ||||
|         this.setState({ | ||||
|             view: VIEWS.LOGGED_IN, | ||||
|         }); | ||||
| 
 | ||||
|         this.setStateForNewView({view: VIEWS.LOGGED_IN}); | ||||
|         if (teamToken) { | ||||
|             // A team member has logged in, not a guest
 | ||||
|             this._teamToken = teamToken; | ||||
|  |  | |||
|  | @ -163,6 +163,7 @@ module.exports = React.createClass({ | |||
|         MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); | ||||
|         MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); | ||||
|         MatrixClientPeg.get().on("accountData", this.onAccountData); | ||||
|         MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus); | ||||
|         this._fetchMediaConfig(); | ||||
|         // Start listening for RoomViewStore updates
 | ||||
|         this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); | ||||
|  | @ -451,6 +452,7 @@ module.exports = React.createClass({ | |||
|             MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership); | ||||
|             MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); | ||||
|             MatrixClientPeg.get().removeListener("accountData", this.onAccountData); | ||||
|             MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus); | ||||
|         } | ||||
| 
 | ||||
|         window.removeEventListener('beforeunload', this.onPageUnload); | ||||
|  | @ -620,6 +622,11 @@ module.exports = React.createClass({ | |||
|                 false, | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onKeyBackupStatus() { | ||||
|         // Key backup status changes affect whether the in-room recovery
 | ||||
|         // reminder is displayed.
 | ||||
|         this.forceUpdate(); | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -162,6 +162,18 @@ module.exports = React.createClass({ | |||
|         this.setState(newState); | ||||
|     }, | ||||
| 
 | ||||
|     onLoginClick: function(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         this.props.onLoginClick(); | ||||
|     }, | ||||
| 
 | ||||
|     onRegisterClick: function(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         this.props.onRegisterClick(); | ||||
|     }, | ||||
| 
 | ||||
|     showErrorDialog: function(body, title) { | ||||
|         const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|         Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { | ||||
|  | @ -253,10 +265,10 @@ module.exports = React.createClass({ | |||
|                     </form> | ||||
|                     { serverConfigSection } | ||||
|                     { errorText } | ||||
|                     <a className="mx_Login_create" onClick={this.props.onLoginClick} href="#"> | ||||
|                     <a className="mx_Login_create" onClick={this.onLoginClick} href="#"> | ||||
|                         { _t('Return to login screen') } | ||||
|                     </a> | ||||
|                     <a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#"> | ||||
|                     <a className="mx_Login_create" onClick={this.onRegisterClick} href="#"> | ||||
|                         { _t('Create an account') } | ||||
|                     </a> | ||||
|                     <LanguageSelector /> | ||||
|  |  | |||
|  | @ -214,7 +214,10 @@ module.exports = React.createClass({ | |||
|         }).done(); | ||||
|     }, | ||||
| 
 | ||||
|     _onLoginAsGuestClick: function() { | ||||
|     _onLoginAsGuestClick: function(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
| 
 | ||||
|         const self = this; | ||||
|         self.setState({ | ||||
|             busy: true, | ||||
|  | @ -297,6 +300,12 @@ module.exports = React.createClass({ | |||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     onRegisterClick: function(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         this.props.onRegisterClick(); | ||||
|     }, | ||||
| 
 | ||||
|     _tryWellKnownDiscovery: async function(serverName) { | ||||
|         if (!serverName.trim()) { | ||||
|             // Nothing to discover
 | ||||
|  | @ -567,7 +576,7 @@ module.exports = React.createClass({ | |||
|                         { errorTextSection } | ||||
|                         { this.componentForStep(this.state.currentFlow) } | ||||
|                         { serverConfig } | ||||
|                         <a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#"> | ||||
|                         <a className="mx_Login_create" onClick={this.onRegisterClick} href="#"> | ||||
|                             { _t('Create an account') } | ||||
|                         </a> | ||||
|                         { loginAsGuestJsx } | ||||
|  |  | |||
|  | @ -363,6 +363,12 @@ module.exports = React.createClass({ | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onLoginClick: function(ev) { | ||||
|         ev.preventDefault(); | ||||
|         ev.stopPropagation(); | ||||
|         this.props.onLoginClick(); | ||||
|     }, | ||||
| 
 | ||||
|     _makeRegisterRequest: function(auth) { | ||||
|         // Only send the bind params if we're sending username / pw params
 | ||||
|         // (Since we need to send no params at all to use the ones saved in the
 | ||||
|  | @ -468,7 +474,7 @@ module.exports = React.createClass({ | |||
|         let signIn; | ||||
|         if (!this.state.doingUIAuth) { | ||||
|             signIn = ( | ||||
|                 <a className="mx_Login_create" onClick={this.props.onLoginClick} href="#"> | ||||
|                 <a className="mx_Login_create" onClick={this.onLoginClick} href="#"> | ||||
|                     { theme === 'status' ? _t('Sign in') : _t('I already have an account') } | ||||
|                 </a> | ||||
|             ); | ||||
|  |  | |||
|  | @ -0,0 +1,120 @@ | |||
| /* | ||||
| Copyright 2018 New Vector 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. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import MatrixClientPeg from '../../../MatrixClientPeg'; | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import MemberAvatar from '../avatars/MemberAvatar'; | ||||
| import classNames from 'classnames'; | ||||
| import * as ContextualMenu from "../../structures/ContextualMenu"; | ||||
| import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu"; | ||||
| import SettingsStore from "../../../settings/SettingsStore"; | ||||
| 
 | ||||
| export default class MemberStatusMessageAvatar extends React.Component { | ||||
|     static propTypes = { | ||||
|         member: PropTypes.object.isRequired, | ||||
|         width: PropTypes.number, | ||||
|         height: PropTypes.number, | ||||
|         resizeMethod: PropTypes.string, | ||||
|     }; | ||||
| 
 | ||||
|     static defaultProps = { | ||||
|         width: 40, | ||||
|         height: 40, | ||||
|         resizeMethod: 'crop', | ||||
|     }; | ||||
| 
 | ||||
|     constructor(props, context) { | ||||
|         super(props, context); | ||||
|     } | ||||
| 
 | ||||
|     componentWillMount() { | ||||
|         if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) { | ||||
|             throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); | ||||
| 
 | ||||
|         if (this.props.member.user) { | ||||
|             this.setState({message: this.props.member.user._unstable_statusMessage}); | ||||
|         } else { | ||||
|             this.setState({message: ""}); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         if (MatrixClientPeg.get()) { | ||||
|             MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _onRoomStateEvents = (ev, state) => { | ||||
|         if (ev.getStateKey() !== MatrixClientPeg.get().getUserId()) return; | ||||
|         if (ev.getType() !== "im.vector.user_status") return; | ||||
|         // TODO: We should be relying on `this.props.member.user._unstable_statusMessage`
 | ||||
|         // We don't currently because the js-sdk doesn't emit a specific event for this
 | ||||
|         // change, and we don't want to race it. This should be improved when we rip out
 | ||||
|         // the im.vector.user_status stuff and replace it with a complete solution.
 | ||||
|         this.setState({message: ev.getContent()["status"]}); | ||||
|     }; | ||||
| 
 | ||||
|     _onClick = (e) => { | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         const elementRect = e.target.getBoundingClientRect(); | ||||
| 
 | ||||
|         // The window X and Y offsets are to adjust position when zoomed in to page
 | ||||
|         const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3; | ||||
|         const chevronOffset = 12; | ||||
|         let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset; | ||||
|         y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
 | ||||
| 
 | ||||
|         ContextualMenu.createMenu(StatusMessageContextMenu, { | ||||
|             chevronOffset: chevronOffset, | ||||
|             chevronFace: 'bottom', | ||||
|             left: x, | ||||
|             top: y, | ||||
|             menuWidth: 190, | ||||
|             user: this.props.member.user, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         if (!SettingsStore.isFeatureEnabled("feature_custom_status")) { | ||||
|             return <MemberAvatar member={this.props.member} | ||||
|                                  width={this.props.width} | ||||
|                                  height={this.props.height} | ||||
|                                  resizeMethod={this.props.resizeMethod} />; | ||||
|         } | ||||
| 
 | ||||
|         const hasStatus = this.props.member.user ? !!this.props.member.user._unstable_statusMessage : false; | ||||
| 
 | ||||
|         const classes = classNames({ | ||||
|             "mx_MemberStatusMessageAvatar": true, | ||||
|             "mx_MemberStatusMessageAvatar_hasStatus": hasStatus, | ||||
|         }); | ||||
| 
 | ||||
|         return <AccessibleButton onClick={this._onClick} className={classes} element="div"> | ||||
|             <MemberAvatar member={this.props.member} | ||||
|                           width={this.props.width} | ||||
|                           height={this.props.height} | ||||
|                           resizeMethod={this.props.resizeMethod} /> | ||||
|         </AccessibleButton>; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,86 @@ | |||
| /* | ||||
| Copyright 2018 New Vector 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. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import MatrixClientPeg from '../../../MatrixClientPeg'; | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| export default class StatusMessageContextMenu extends React.Component { | ||||
|     static propTypes = { | ||||
|         // js-sdk User object. Not required because it might not exist.
 | ||||
|         user: PropTypes.object, | ||||
|     }; | ||||
| 
 | ||||
|     constructor(props, context) { | ||||
|         super(props, context); | ||||
| 
 | ||||
|         this.state = { | ||||
|             message: props.user ? props.user._unstable_statusMessage : "", | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     _onClearClick = async(e) => { | ||||
|         await MatrixClientPeg.get()._unstable_setStatusMessage(""); | ||||
|         this.setState({message: ""}); | ||||
|     }; | ||||
| 
 | ||||
|     _onSubmit = (e) => { | ||||
|         e.preventDefault(); | ||||
|         MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message); | ||||
|     }; | ||||
| 
 | ||||
|     _onStatusChange = (e) => { | ||||
|         this.setState({message: e.target.value}); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const formSubmitClasses = classNames({ | ||||
|             "mx_StatusMessageContextMenu_submit": true, | ||||
|             "mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
 | ||||
|         }); | ||||
| 
 | ||||
|         const form = <form className="mx_StatusMessageContextMenu_form" onSubmit={this._onSubmit} autoComplete="off"> | ||||
|             <input type="text" key="message" placeholder={_t("Set a new status...")} autoFocus={true} | ||||
|                    className="mx_StatusMessageContextMenu_message" | ||||
|                    value={this.state.message} onChange={this._onStatusChange} maxLength="60" /> | ||||
|             <AccessibleButton onClick={this._onSubmit} element="div" className={formSubmitClasses}> | ||||
|                 <img src="img/icons-checkmark.svg" width="22" height="22" /> | ||||
|             </AccessibleButton> | ||||
|         </form>; | ||||
| 
 | ||||
|         const clearIcon = this.state.message ? "img/cancel-red.svg" : "img/cancel.svg"; | ||||
|         const clearButton = <AccessibleButton onClick={this._onClearClick} disabled={!this.state.message} | ||||
|                                               className="mx_StatusMessageContextMenu_clear"> | ||||
|             <img src={clearIcon} alt={_t('Clear status')} width="12" height="12" | ||||
|                  className="mx_filterFlipColor mx_StatusMessageContextMenu_clearIcon" /> | ||||
|             <span>{_t("Clear status")}</span> | ||||
|         </AccessibleButton>; | ||||
| 
 | ||||
|         const menuClasses = classNames({ | ||||
|             "mx_StatusMessageContextMenu": true, | ||||
|             "mx_StatusMessageContextMenu_hasStatus": this.state.message, | ||||
|         }); | ||||
| 
 | ||||
|         return <div className={menuClasses}> | ||||
|             { form } | ||||
|             <hr /> | ||||
|             { clearButton } | ||||
|         </div>; | ||||
|     } | ||||
| } | ||||
|  | @ -36,8 +36,12 @@ export default class ChangelogDialog extends React.Component { | |||
|         for (let i=0; i<REPOS.length; i++) { | ||||
|             const oldVersion = version2[2*i]; | ||||
|             const newVersion = version[2*i]; | ||||
|             request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => { | ||||
|                 if (body == null) return; | ||||
|             const url = `https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`; | ||||
|             request(url, (err, response, body) => { | ||||
|                 if (response.statusCode < 200 || response.statusCode >= 300) { | ||||
|                     this.setState({ [REPOS[i]]: response.statusText }); | ||||
|                     return; | ||||
|                 } | ||||
|                 this.setState({[REPOS[i]]: JSON.parse(body).commits}); | ||||
|             }); | ||||
|         } | ||||
|  | @ -58,13 +62,20 @@ export default class ChangelogDialog extends React.Component { | |||
|         const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); | ||||
| 
 | ||||
|         const logs = REPOS.map(repo => { | ||||
|             if (this.state[repo] == null) return <Spinner key={repo} />; | ||||
|             let content; | ||||
|             if (this.state[repo] == null) { | ||||
|                 content = <Spinner key={repo} />; | ||||
|             } else if (typeof this.state[repo] === "string") { | ||||
|                 content = _t("Unable to load commit detail: %(msg)s", { | ||||
|                     msg: this.state[repo], | ||||
|                 }); | ||||
|             } else { | ||||
|                 content = this.state[repo].map(this._elementsForCommit); | ||||
|             } | ||||
|             return ( | ||||
|                 <div key={repo}> | ||||
|                     <h2>{repo}</h2> | ||||
|                     <ul> | ||||
|                     {this.state[repo].map(this._elementsForCommit)} | ||||
|                     </ul> | ||||
|                     <ul>{content}</ul> | ||||
|                 </div> | ||||
|             ); | ||||
|         }); | ||||
|  |  | |||
|  | @ -29,7 +29,6 @@ var TintableSvg = React.createClass({ | |||
|         width: PropTypes.string.isRequired, | ||||
|         height: PropTypes.string.isRequired, | ||||
|         className: PropTypes.string, | ||||
|         forceColors: PropTypes.arrayOf(PropTypes.string), | ||||
|     }, | ||||
| 
 | ||||
|     statics: { | ||||
|  | @ -51,12 +50,6 @@ var TintableSvg = React.createClass({ | |||
|         delete TintableSvg.mounts[this.id]; | ||||
|     }, | ||||
| 
 | ||||
|     componentDidUpdate: function(prevProps, prevState) { | ||||
|         if (prevProps.forceColors !== this.props.forceColors) { | ||||
|             this.calcAndApplyFixups(this.refs.svgContainer); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     tint: function() { | ||||
|         // TODO: only bother running this if the global tint settings have changed
 | ||||
|         // since we loaded!
 | ||||
|  | @ -64,13 +57,8 @@ var TintableSvg = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     onLoad: function(event) { | ||||
|         this.calcAndApplyFixups(event.target); | ||||
|     }, | ||||
| 
 | ||||
|     calcAndApplyFixups: function(target) { | ||||
|         if (!target) return; | ||||
|         // console.log("TintableSvg.calcAndApplyFixups for " + this.props.src);
 | ||||
|         this.fixups = Tinter.calcSvgFixups([target], this.props.forceColors); | ||||
|         // console.log("TintableSvg.onLoad for " + this.props.src);
 | ||||
|         this.fixups = Tinter.calcSvgFixups([event.target]); | ||||
|         Tinter.applySvgFixups(this.fixups); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -83,7 +71,6 @@ var TintableSvg = React.createClass({ | |||
|                     height={this.props.height} | ||||
|                     onLoad={this.onLoad} | ||||
|                     tabIndex="-1" | ||||
|                     ref="svgContainer" | ||||
|                 /> | ||||
|         ); | ||||
|     }, | ||||
|  |  | |||
|  | @ -85,8 +85,8 @@ export default React.createClass({ | |||
|     _getDisplayedGroups(userGroups, relatedGroups) { | ||||
|         let displayedGroups = userGroups || []; | ||||
|         if (relatedGroups && relatedGroups.length > 0) { | ||||
|             displayedGroups = displayedGroups.filter((groupId) => { | ||||
|                 return relatedGroups.includes(groupId); | ||||
|             displayedGroups = relatedGroups.filter((groupId) => { | ||||
|                 return displayedGroups.includes(groupId); | ||||
|             }); | ||||
|         } else { | ||||
|             displayedGroups = []; | ||||
|  |  | |||
|  | @ -70,6 +70,7 @@ const EntityTile = React.createClass({ | |||
|         onClick: PropTypes.func, | ||||
|         suppressOnHover: PropTypes.bool, | ||||
|         showPresence: PropTypes.bool, | ||||
|         subtextLabel: PropTypes.string, | ||||
|     }, | ||||
| 
 | ||||
|     getDefaultProps: function() { | ||||
|  | @ -129,6 +130,9 @@ const EntityTile = React.createClass({ | |||
|                     presenceState={this.props.presenceState} />; | ||||
|                 nameClasses += ' mx_EntityTile_name_hover'; | ||||
|             } | ||||
|             if (this.props.subtextLabel) { | ||||
|                 presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>; | ||||
|             } | ||||
|             nameEl = ( | ||||
|                 <div className="mx_EntityTile_details"> | ||||
|                     <EmojiText element="div" className={nameClasses} dir="auto"> | ||||
|  | @ -137,6 +141,15 @@ const EntityTile = React.createClass({ | |||
|                     {presenceLabel} | ||||
|                 </div> | ||||
|             ); | ||||
|         } else if (this.props.subtextLabel) { | ||||
|             nameEl = ( | ||||
|                 <div className="mx_EntityTile_details"> | ||||
|                     <EmojiText element="div" className="mx_EntityTile_name" dir="auto"> | ||||
|                         {name} | ||||
|                     </EmojiText> | ||||
|                     <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span> | ||||
|                 </div> | ||||
|             ); | ||||
|         } else { | ||||
|             nameEl = ( | ||||
|                 <EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText> | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ import AccessibleButton from '../elements/AccessibleButton'; | |||
| import RoomViewStore from '../../../stores/RoomViewStore'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import MultiInviter from "../../../utils/MultiInviter"; | ||||
| import SettingsStore from "../../../settings/SettingsStore"; | ||||
| 
 | ||||
| module.exports = withMatrixClient(React.createClass({ | ||||
|     displayName: 'MemberInfo', | ||||
|  | @ -889,11 +890,16 @@ module.exports = withMatrixClient(React.createClass({ | |||
|         let presenceState; | ||||
|         let presenceLastActiveAgo; | ||||
|         let presenceCurrentlyActive; | ||||
|         let statusMessage; | ||||
| 
 | ||||
|         if (this.props.member.user) { | ||||
|             presenceState = this.props.member.user.presence; | ||||
|             presenceLastActiveAgo = this.props.member.user.lastActiveAgo; | ||||
|             presenceCurrentlyActive = this.props.member.user.currentlyActive; | ||||
| 
 | ||||
|             if (SettingsStore.isFeatureEnabled("feature_custom_status")) { | ||||
|                 statusMessage = this.props.member.user._unstable_statusMessage; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const room = this.props.matrixClient.getRoom(this.props.member.roomId); | ||||
|  | @ -915,6 +921,11 @@ module.exports = withMatrixClient(React.createClass({ | |||
|                 presenceState={presenceState} />; | ||||
|         } | ||||
| 
 | ||||
|         let statusLabel = null; | ||||
|         if (statusMessage) { | ||||
|             statusLabel = <span className="mx_MemberInfo_statusMessage">{ statusMessage }</span>; | ||||
|         } | ||||
| 
 | ||||
|         let roomMemberDetails = null; | ||||
|         if (this.props.member.roomId) { // is in room
 | ||||
|             const PowerSelector = sdk.getComponent('elements.PowerSelector'); | ||||
|  | @ -931,6 +942,7 @@ module.exports = withMatrixClient(React.createClass({ | |||
|                 </div> | ||||
|                 <div className="mx_MemberInfo_profileField"> | ||||
|                     {presenceLabel} | ||||
|                     {statusLabel} | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } | ||||
|  |  | |||
|  | @ -16,6 +16,8 @@ limitations under the License. | |||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| import SettingsStore from "../../../settings/SettingsStore"; | ||||
| 
 | ||||
| const React = require('react'); | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
|  | @ -85,6 +87,11 @@ module.exports = React.createClass({ | |||
|         const active = -1; | ||||
|         const presenceState = member.user ? member.user.presence : null; | ||||
| 
 | ||||
|         let statusMessage = null; | ||||
|         if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) { | ||||
|             statusMessage = member.user._unstable_statusMessage; | ||||
|         } | ||||
| 
 | ||||
|         const av = ( | ||||
|             <MemberAvatar member={member} width={36} height={36} /> | ||||
|         ); | ||||
|  | @ -106,7 +113,9 @@ module.exports = React.createClass({ | |||
|                 presenceLastTs={member.user ? member.user.lastPresenceTs : 0} | ||||
|                 presenceCurrentlyActive={member.user ? member.user.currentlyActive : false} | ||||
|                 avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick} | ||||
|                 name={name} powerStatus={powerStatus} showPresence={this.props.showPresence} /> | ||||
|                 name={name} powerStatus={powerStatus} showPresence={this.props.showPresence} | ||||
|                 subtextLabel={statusMessage} | ||||
|             /> | ||||
|         ); | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -291,7 +291,7 @@ export default class MessageComposer extends React.Component { | |||
| 
 | ||||
|     render() { | ||||
|         const uploadInputStyle = {display: 'none'}; | ||||
|         const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); | ||||
|         const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); | ||||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
|         const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); | ||||
| 
 | ||||
|  | @ -300,7 +300,7 @@ export default class MessageComposer extends React.Component { | |||
|         if (this.state.me) { | ||||
|             controls.push( | ||||
|                 <div key="controls_avatar" className="mx_MessageComposer_avatar"> | ||||
|                     <MemberAvatar member={this.state.me} width={24} height={24} /> | ||||
|                     <MemberStatusMessageAvatar member={this.state.me} width={24} height={24} /> | ||||
|                 </div>, | ||||
|             ); | ||||
|         } | ||||
|  | @ -349,6 +349,34 @@ export default class MessageComposer extends React.Component { | |||
|         const canSendMessages = !this.state.tombstone && | ||||
|             this.props.room.maySendMessage(); | ||||
| 
 | ||||
|         // TODO: Remove temporary logging for riot-web#7838
 | ||||
|         // Note: we rip apart the power level event ourselves because we don't want to
 | ||||
|         // log too much data about it - just the bits we care about. Many of the variables
 | ||||
|         // logged here are to help figure out where in the stack the 'cannot post in room'
 | ||||
|         // warning is coming from. This means logging various numbers from the PL event to
 | ||||
|         // verify RoomState._maySendEventOfType is doing the right thing.
 | ||||
|         const room = this.props.room; | ||||
|         const plEvent = room.currentState.getStateEvents('m.room.power_levels', ''); | ||||
|         let plEventString = "<no power level event>"; | ||||
|         if (plEvent) { | ||||
|             const content = plEvent.getContent(); | ||||
|             if (!content) { | ||||
|                 plEventString = "<no event content>"; | ||||
|             } else { | ||||
|                 const stringifyFalsey = (v) => v === null ? '<null>' : (v === undefined ? '<undefined>' : v); | ||||
|                 const actualUserPl = stringifyFalsey(content.users ? content.users[room.myUserId] : "<no users in content>"); | ||||
|                 const usersPl = stringifyFalsey(content.users_default); | ||||
|                 const actualEventPl = stringifyFalsey(content.events ? content.events['m.room.message'] : "<no events in content>"); | ||||
|                 const eventPl = stringifyFalsey(content.events_default); | ||||
|                 plEventString = `actualUserPl=${actualUserPl} defaultUserPl=${usersPl} actualEventPl=${actualEventPl} defaultEventPl=${eventPl}`; | ||||
|             } | ||||
|         } | ||||
|         console.log( | ||||
|             `[riot-web#7838] renderComposer() hasTombstone=${!!this.state.tombstone} maySendMessage=${room.maySendMessage()}` + | ||||
|             ` myMembership=${room.getMyMembership()} maySendEvent=${room.currentState.maySendEvent('m.room.message', room.myUserId)}` + | ||||
|             ` myUserId=${room.myUserId} roomId=${room.roomId} hasPlEvent=${!!plEvent} powerLevels='${plEventString}'` | ||||
|         ); | ||||
| 
 | ||||
|         if (canSendMessages) { | ||||
|             // This also currently includes the call buttons. Really we should
 | ||||
|             // check separately for whether we can call, but this is slightly
 | ||||
|  | @ -425,6 +453,8 @@ export default class MessageComposer extends React.Component { | |||
|                 </div> | ||||
|             </div>); | ||||
|         } else { | ||||
|             // TODO: Remove temporary logging for riot-web#7838
 | ||||
|             console.log("[riot-web#7838] Falling back to showing cannot post in room error"); | ||||
|             controls.push( | ||||
|                 <div key="controls_error" className="mx_MessageComposer_noperm_error"> | ||||
|                     { _t('You do not have permission to post to this room') } | ||||
|  |  | |||
|  | @ -86,6 +86,7 @@ module.exports = React.createClass({ | |||
|             incomingCallTag: null, | ||||
|             incomingCall: null, | ||||
|             selectedTags: [], | ||||
|             hover: false, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -294,6 +295,17 @@ module.exports = React.createClass({ | |||
|         this.forceUpdate(); | ||||
|     }, | ||||
| 
 | ||||
|     onMouseEnter: function(ev) { | ||||
|         this.setState({hover: true}); | ||||
|     }, | ||||
| 
 | ||||
|     onMouseLeave: function(ev) { | ||||
|         this.setState({hover: false}); | ||||
| 
 | ||||
|         // Refresh the room list just in case the user missed something.
 | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     _delayedRefreshRoomList: new rate_limited_func(function() { | ||||
|         this.refreshRoomList(); | ||||
|     }, 500), | ||||
|  | @ -346,6 +358,11 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     refreshRoomList: function() { | ||||
|         if (this.state.hover) { | ||||
|             // Don't re-sort the list if we're hovering over the list
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // TODO: ideally we'd calculate this once at start, and then maintain
 | ||||
|         // any changes to it incrementally, updating the appropriate sublists
 | ||||
|         // as needed.
 | ||||
|  | @ -693,9 +710,10 @@ module.exports = React.createClass({ | |||
|         const subListComponents = this._mapSubListProps(subLists); | ||||
| 
 | ||||
|         return ( | ||||
|             <div ref={this._collectResizeContainer} className="mx_RoomList"> | ||||
|             <div ref={this._collectResizeContainer} className="mx_RoomList" | ||||
|                  onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> | ||||
|                 { subListComponents } | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
| }); | ||||
| }); | ||||
|  | @ -19,13 +19,76 @@ import PropTypes from "prop-types"; | |||
| import sdk from "../../../index"; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import Modal from "../../../Modal"; | ||||
| import MatrixClientPeg from "../../../MatrixClientPeg"; | ||||
| 
 | ||||
| export default class RoomRecoveryReminder extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     } | ||||
| 
 | ||||
|     showKeyBackupDialog = () => { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this.state = { | ||||
|             loading: true, | ||||
|             error: null, | ||||
|             unverifiedDevice: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentWillMount() { | ||||
|         this._loadBackupStatus(); | ||||
|     } | ||||
| 
 | ||||
|     async _loadBackupStatus() { | ||||
|         let backupSigStatus; | ||||
|         try { | ||||
|             const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); | ||||
|             backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); | ||||
|         } catch (e) { | ||||
|             console.log("Unable to fetch key backup status", e); | ||||
|             this.setState({ | ||||
|                 loading: false, | ||||
|                 error: e, | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let unverifiedDevice; | ||||
|         for (const sig of backupSigStatus.sigs) { | ||||
|             if (!sig.device.isVerified()) { | ||||
|                 unverifiedDevice = sig.device; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         this.setState({ | ||||
|             loading: false, | ||||
|             unverifiedDevice, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     showSetupDialog = () => { | ||||
|         if (this.state.unverifiedDevice) { | ||||
|             // A key backup exists for this account, but the creating device is not
 | ||||
|             // verified, so we'll show the device verify dialog.
 | ||||
|             // TODO: Should change to a restore key backup flow that checks the recovery
 | ||||
|             // passphrase while at the same time also cross-signing the device as well in
 | ||||
|             // a single flow (for cases where a key backup exists but the backup creating
 | ||||
|             // device is unverified).  Since we don't have that yet, we'll look for an
 | ||||
|             // unverified device and verify it.  Note that this means we won't restore
 | ||||
|             // keys yet; instead we'll only trust the backup for sending our own new keys
 | ||||
|             // to it.
 | ||||
|             const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); | ||||
|             Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { | ||||
|                 userId: MatrixClientPeg.get().credentials.userId, | ||||
|                 device: this.state.unverifiedDevice, | ||||
|                 onFinished: this.props.onFinished, | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // The default case assumes that a key backup doesn't exist for this account, so
 | ||||
|         // we'll show the create key backup flow.
 | ||||
|         Modal.createTrackedDialogAsync("Key Backup", "Key Backup", | ||||
|             import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), | ||||
|             { | ||||
|  | @ -46,29 +109,51 @@ export default class RoomRecoveryReminder extends React.PureComponent { | |||
|                     this.props.onFinished(false); | ||||
|                 }, | ||||
|                 onSetup: () => { | ||||
|                     this.showKeyBackupDialog(); | ||||
|                     this.showSetupDialog(); | ||||
|                 }, | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     onSetupClick = () => { | ||||
|         this.showKeyBackupDialog(); | ||||
|         this.showSetupDialog(); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         if (this.state.loading) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); | ||||
| 
 | ||||
|         let body; | ||||
|         if (this.state.error) { | ||||
|             body = <div className="error"> | ||||
|                 {_t("Unable to load key backup status")} | ||||
|             </div>; | ||||
|         } else if (this.state.unverifiedDevice) { | ||||
|             // A key backup exists for this account, but the creating device is not
 | ||||
|             // verified.
 | ||||
|             body = _t( | ||||
|                 "To view your secure message history and ensure you can view new " + | ||||
|                 "messages on future devices, set up Secure Message Recovery.", | ||||
|             ); | ||||
|         } else { | ||||
|             // The default case assumes that a key backup doesn't exist for this account.
 | ||||
|             // (This component doesn't currently check that itself.)
 | ||||
|             body = _t( | ||||
|                 "If you log out or use another device, you'll lose your " + | ||||
|                 "secure message history. To prevent this, set up Secure " + | ||||
|                 "Message Recovery.", | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div className="mx_RoomRecoveryReminder"> | ||||
|                 <div className="mx_RoomRecoveryReminder_header">{_t( | ||||
|                     "Secure Message Recovery", | ||||
|                 )}</div> | ||||
|                 <div className="mx_RoomRecoveryReminder_body">{_t( | ||||
|                     "If you log out or use another device, you'll lose your " + | ||||
|                     "secure message history. To prevent this, set up Secure " + | ||||
|                     "Message Recovery.", | ||||
|                 )}</div> | ||||
|                 <div className="mx_RoomRecoveryReminder_body">{body}</div> | ||||
|                 <div className="mx_RoomRecoveryReminder_buttons"> | ||||
|                     <AccessibleButton className="mx_RoomRecoveryReminder_button mx_RoomRecoveryReminder_secondary" | ||||
|                         onClick={this.onDontAskAgainClick}> | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ import * as FormattingUtils from '../../../utils/FormattingUtils'; | |||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import ActiveRoomObserver from '../../../ActiveRoomObserver'; | ||||
| import RoomViewStore from '../../../stores/RoomViewStore'; | ||||
| import SettingsStore from "../../../settings/SettingsStore"; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RoomTile', | ||||
|  | @ -251,6 +252,17 @@ module.exports = React.createClass({ | |||
|         const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); | ||||
|         const badges = notifBadges || mentionBadges; | ||||
| 
 | ||||
|         const isJoined = this.props.room.getMyMembership() === "join"; | ||||
|         const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2; | ||||
|         let subtext = null; | ||||
|         if (!isInvite && isJoined && looksLikeDm && SettingsStore.isFeatureEnabled("feature_custom_status")) { | ||||
|             const selfId = MatrixClientPeg.get().getUserId(); | ||||
|             const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0]; | ||||
|             if (otherMember && otherMember.user && otherMember.user._unstable_statusMessage) { | ||||
|                 subtext = otherMember.user._unstable_statusMessage; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const classes = classNames({ | ||||
|             'mx_RoomTile': true, | ||||
|             'mx_RoomTile_selected': this.state.selected, | ||||
|  | @ -261,6 +273,7 @@ module.exports = React.createClass({ | |||
|             'mx_RoomTile_menuDisplayed': this.state.menuDisplayed, | ||||
|             'mx_RoomTile_noBadges': !badges, | ||||
|             'mx_RoomTile_transparent': this.props.transparent, | ||||
|             'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, | ||||
|         }); | ||||
| 
 | ||||
|         const avatarClasses = classNames({ | ||||
|  | @ -286,6 +299,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         const EmojiText = sdk.getComponent('elements.EmojiText'); | ||||
|         let label; | ||||
|         let subtextLabel; | ||||
|         let tooltip; | ||||
|         if (!this.props.collapsed) { | ||||
|             const nameClasses = classNames({ | ||||
|  | @ -294,6 +308,8 @@ module.exports = React.createClass({ | |||
|                 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, | ||||
|             }); | ||||
| 
 | ||||
|             subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null; | ||||
| 
 | ||||
|             if (this.state.selected) { | ||||
|                 const nameSelected = <EmojiText>{ name }</EmojiText>; | ||||
| 
 | ||||
|  | @ -337,9 +353,14 @@ module.exports = React.createClass({ | |||
|                     { dmIndicator } | ||||
|                 </div> | ||||
|             </div> | ||||
|             { label } | ||||
|             { contextMenuButton } | ||||
|             { badge } | ||||
|             <div className="mx_RoomTile_nameContainer"> | ||||
|                 <div className="mx_RoomTile_labelContainer"> | ||||
|                     { label } | ||||
|                     { subtextLabel } | ||||
|                 </div> | ||||
|                 { contextMenuButton } | ||||
|                 { badge } | ||||
|             </div> | ||||
|             { /* { incomingCallBox } */ } | ||||
|             { tooltip } | ||||
|         </AccessibleButton>; | ||||
|  |  | |||
|  | @ -250,11 +250,14 @@ | |||
|     "A word by itself is easy to guess": "A word by itself is easy to guess", | ||||
|     "Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess", | ||||
|     "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", | ||||
|     "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", | ||||
|     "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", | ||||
|     "There was an error joining the room": "There was an error joining the room", | ||||
|     "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", | ||||
|     "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", | ||||
|     "Failed to join room": "Failed to join room", | ||||
|     "Message Pinning": "Message Pinning", | ||||
|     "Custom user status messages": "Custom user status messages", | ||||
|     "Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view", | ||||
|     "Backup of encryption keys to server": "Backup of encryption keys to server", | ||||
|     "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", | ||||
|  | @ -563,8 +566,9 @@ | |||
|     "You are trying to access a room.": "You are trying to access a room.", | ||||
|     "<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!", | ||||
|     "This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled", | ||||
|     "Secure Message Recovery": "Secure Message Recovery", | ||||
|     "To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.": "To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.", | ||||
|     "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.": "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.", | ||||
|     "Secure Message Recovery": "Secure Message Recovery", | ||||
|     "Don't ask again": "Don't ask again", | ||||
|     "Set up": "Set up", | ||||
|     "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", | ||||
|  | @ -888,6 +892,7 @@ | |||
|     "What GitHub issue are these logs for?": "What GitHub issue are these logs for?", | ||||
|     "Notes:": "Notes:", | ||||
|     "Send logs": "Send logs", | ||||
|     "Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s", | ||||
|     "Unavailable": "Unavailable", | ||||
|     "Changelog": "Changelog", | ||||
|     "Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one", | ||||
|  | @ -1064,6 +1069,8 @@ | |||
|     "Forget": "Forget", | ||||
|     "Low Priority": "Low Priority", | ||||
|     "Direct Chat": "Direct Chat", | ||||
|     "Set a new status...": "Set a new status...", | ||||
|     "Clear status": "Clear status", | ||||
|     "View Community": "View Community", | ||||
|     "Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.", | ||||
|     "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", | ||||
|  |  | |||
|  | @ -83,6 +83,12 @@ export const SETTINGS = { | |||
|         supportedLevels: LEVELS_FEATURE, | ||||
|         default: false, | ||||
|     }, | ||||
|     "feature_custom_status": { | ||||
|         isFeature: true, | ||||
|         displayName: _td("Custom user status messages"), | ||||
|         supportedLevels: LEVELS_FEATURE, | ||||
|         default: false, | ||||
|     }, | ||||
|     "feature_lazyloading": { | ||||
|         isFeature: true, | ||||
|         displayName: _td("Increase performance by only loading room members on first view"), | ||||
|  |  | |||
|  | @ -52,6 +52,8 @@ _td("This is similar to a commonly used password"); | |||
| _td("A word by itself is easy to guess"); | ||||
| _td("Names and surnames by themselves are easy to guess"); | ||||
| _td("Common names and surnames are easy to guess"); | ||||
| _td("Straight rows of keys are easy to guess"); | ||||
| _td("Short keyboard patterns are easy to guess"); | ||||
| 
 | ||||
| /** | ||||
|  * Wrapper around zxcvbn password strength estimation | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ describe('DecryptionFailureTracker', function() { | |||
|         // Immediately track the newest failures
 | ||||
|         tracker.trackFailures(); | ||||
| 
 | ||||
|         expect(count).toNotBe(0, 'should track a failure for an event that failed decryption'); | ||||
|         expect(count).not.toBe(0, 'should track a failure for an event that failed decryption'); | ||||
| 
 | ||||
|         done(); | ||||
|     }); | ||||
|  |  | |||
|  | @ -185,21 +185,21 @@ describe('GroupView', function() { | |||
|             const avatar = ReactTestUtils.findRenderedComponentWithType(root, sdk.getComponent('avatars.GroupAvatar')); | ||||
|             const img = ReactTestUtils.findRenderedDOMComponentWithTag(avatar, 'img'); | ||||
|             const avatarImgElement = ReactDOM.findDOMNode(img); | ||||
|             expect(avatarImgElement).toExist(); | ||||
|             expect(avatarImgElement.src).toInclude( | ||||
|             expect(avatarImgElement).toBeTruthy(); | ||||
|             expect(avatarImgElement.src).toContain( | ||||
|                 'https://my.home.server/_matrix/media/v1/thumbnail/' + | ||||
|                 'someavatarurl?width=48&height=48&method=crop', | ||||
|             ); | ||||
| 
 | ||||
|             const name = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_name'); | ||||
|             const nameElement = ReactDOM.findDOMNode(name); | ||||
|             expect(nameElement).toExist(); | ||||
|             expect(nameElement.innerText).toInclude('The name of a community'); | ||||
|             expect(nameElement.innerText).toInclude(groupId); | ||||
|             expect(nameElement).toBeTruthy(); | ||||
|             expect(nameElement.innerText).toContain('The name of a community'); | ||||
|             expect(nameElement.innerText).toContain(groupId); | ||||
| 
 | ||||
|             const shortDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_shortDesc'); | ||||
|             const shortDescElement = ReactDOM.findDOMNode(shortDesc); | ||||
|             expect(shortDescElement).toExist(); | ||||
|             expect(shortDescElement).toBeTruthy(); | ||||
|             expect(shortDescElement.innerText).toBe('This is a community'); | ||||
|         }); | ||||
| 
 | ||||
|  | @ -219,7 +219,7 @@ describe('GroupView', function() { | |||
| 
 | ||||
|             const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc'); | ||||
|             const longDescElement = ReactDOM.findDOMNode(longDesc); | ||||
|             expect(longDescElement).toExist(); | ||||
|             expect(longDescElement).toBeTruthy(); | ||||
|             expect(longDescElement.innerText).toBe('This is a LONG description.'); | ||||
|             expect(longDescElement.innerHTML).toBe('<div dir="auto">This is a <b>LONG</b> description.</div>'); | ||||
|         }); | ||||
|  | @ -239,7 +239,7 @@ describe('GroupView', function() { | |||
|             const placeholder = ReactTestUtils | ||||
|                 .findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc_placeholder'); | ||||
|             const placeholderElement = ReactDOM.findDOMNode(placeholder); | ||||
|             expect(placeholderElement).toExist(); | ||||
|             expect(placeholderElement).toBeTruthy(); | ||||
|         }); | ||||
| 
 | ||||
|         httpBackend | ||||
|  | @ -258,15 +258,15 @@ describe('GroupView', function() { | |||
|         const prom = waitForUpdate(groupView, 4).then(() => { | ||||
|             const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc'); | ||||
|             const longDescElement = ReactDOM.findDOMNode(longDesc); | ||||
|             expect(longDescElement).toExist(); | ||||
|             expect(longDescElement).toBeTruthy(); | ||||
| 
 | ||||
|             expect(longDescElement.innerHTML).toInclude('<h1>This is a more complicated group page</h1>'); | ||||
|             expect(longDescElement.innerHTML).toInclude('<p>With paragraphs</p>'); | ||||
|             expect(longDescElement.innerHTML).toInclude('<ul>'); | ||||
|             expect(longDescElement.innerHTML).toInclude('<li>And lists!</li>'); | ||||
|             expect(longDescElement.innerHTML).toContain('<h1>This is a more complicated group page</h1>'); | ||||
|             expect(longDescElement.innerHTML).toContain('<p>With paragraphs</p>'); | ||||
|             expect(longDescElement.innerHTML).toContain('<ul>'); | ||||
|             expect(longDescElement.innerHTML).toContain('<li>And lists!</li>'); | ||||
| 
 | ||||
|             const imgSrc = "https://my.home.server/_matrix/media/v1/thumbnail/someimageurl?width=800&height=600"; | ||||
|             expect(longDescElement.innerHTML).toInclude('<img src="' + imgSrc + '">'); | ||||
|             expect(longDescElement.innerHTML).toContain('<img src="' + imgSrc + '">'); | ||||
|         }); | ||||
| 
 | ||||
|         httpBackend | ||||
|  | @ -285,11 +285,11 @@ describe('GroupView', function() { | |||
|         const prom = waitForUpdate(groupView, 4).then(() => { | ||||
|             const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc'); | ||||
|             const longDescElement = ReactDOM.findDOMNode(longDesc); | ||||
|             expect(longDescElement).toExist(); | ||||
|             expect(longDescElement).toBeTruthy(); | ||||
| 
 | ||||
|             // If this fails, the URL could be in an img `src`, which is what we care about but
 | ||||
|             // there's no harm in keeping this simple and checking the entire HTML string.
 | ||||
|             expect(longDescElement.innerHTML).toExclude('evilimageurl'); | ||||
|             expect(longDescElement.innerHTML).not.toContain('evilimageurl'); | ||||
|         }); | ||||
| 
 | ||||
|         httpBackend | ||||
|  | @ -308,7 +308,7 @@ describe('GroupView', function() { | |||
|         const prom = waitForUpdate(groupView, 4).then(() => { | ||||
|             const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList'); | ||||
|             const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList); | ||||
|             expect(roomDetailListElement).toExist(); | ||||
|             expect(roomDetailListElement).toBeTruthy(); | ||||
|         }); | ||||
| 
 | ||||
|         httpBackend.when('GET', '/groups/' + groupIdEncoded + '/summary').respond(200, summaryResponse); | ||||
|  | @ -325,7 +325,7 @@ describe('GroupView', function() { | |||
|         const prom = waitForUpdate(groupView, 4).then(() => { | ||||
|             const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList'); | ||||
|             const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList); | ||||
|             expect(roomDetailListElement).toExist(); | ||||
|             expect(roomDetailListElement).toBeTruthy(); | ||||
| 
 | ||||
|             const roomDetailListRoomName = ReactTestUtils.findRenderedDOMComponentWithClass( | ||||
|                 root, | ||||
|  | @ -333,7 +333,7 @@ describe('GroupView', function() { | |||
|             ); | ||||
|             const roomDetailListRoomNameElement = ReactDOM.findDOMNode(roomDetailListRoomName); | ||||
| 
 | ||||
|             expect(roomDetailListRoomNameElement).toExist(); | ||||
|             expect(roomDetailListRoomNameElement).toBeTruthy(); | ||||
|             expect(roomDetailListRoomNameElement.innerText).toEqual('Some room name'); | ||||
|         }); | ||||
| 
 | ||||
|  | @ -364,7 +364,7 @@ describe('GroupView', function() { | |||
|         const prom = waitForUpdate(groupView, 3).then(() => { | ||||
|             const shortDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_shortDesc'); | ||||
|             const shortDescElement = ReactDOM.findDOMNode(shortDesc); | ||||
|             expect(shortDescElement).toExist(); | ||||
|             expect(shortDescElement).toBeTruthy(); | ||||
|             expect(shortDescElement.innerText).toBe('This is a community'); | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| const jest = require('jest-mock'); | ||||
| const React = require('react'); | ||||
| const ReactDOM = require('react-dom'); | ||||
| const ReactTestUtils = require('react-addons-test-utils'); | ||||
|  | @ -87,8 +88,8 @@ describe('Registration', function() { | |||
|     }); | ||||
| 
 | ||||
|     it('should NOT track a referral following successful registration of a non-team member', function(done) { | ||||
|         const onLoggedIn = expect.createSpy().andCall(function(creds, teamToken) { | ||||
|             expect(teamToken).toNotExist(); | ||||
|         const onLoggedIn = jest.fn(function(creds, teamToken) { | ||||
|             expect(teamToken).toBeFalsy(); | ||||
|             done(); | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -83,8 +83,8 @@ describe('InteractiveAuthDialog', function() { | |||
|                     submitNode = node; | ||||
|                 } | ||||
|             } | ||||
|             expect(passwordNode).toExist(); | ||||
|             expect(submitNode).toExist(); | ||||
|             expect(passwordNode).toBeTruthy(); | ||||
|             expect(submitNode).toBeTruthy(); | ||||
| 
 | ||||
|             // submit should be disabled
 | ||||
|             expect(submitNode.disabled).toBe(true); | ||||
|  |  | |||
|  | @ -114,7 +114,7 @@ describe("GroupMemberList", function() { | |||
| 
 | ||||
|             const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined"); | ||||
|             const memberListElement = ReactDOM.findDOMNode(memberList); | ||||
|             expect(memberListElement).toExist(); | ||||
|             expect(memberListElement).toBeTruthy(); | ||||
|             expect(memberListElement.innerText).toBe("Test"); | ||||
|         }); | ||||
| 
 | ||||
|  | @ -134,7 +134,7 @@ describe("GroupMemberList", function() { | |||
| 
 | ||||
|             const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined"); | ||||
|             const memberListElement = ReactDOM.findDOMNode(memberList); | ||||
|             expect(memberListElement).toExist(); | ||||
|             expect(memberListElement).toBeTruthy(); | ||||
|             expect(memberListElement.innerText).toBe("Failed to load group members"); | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| const jest = require('jest-mock'); | ||||
| const React = require('react'); | ||||
| const ReactDOM = require("react-dom"); | ||||
| const ReactTestUtils = require('react-addons-test-utils'); | ||||
|  | @ -55,14 +56,14 @@ function doInputEmail(inputEmail, onTeamSelected) { | |||
| } | ||||
| 
 | ||||
| function expectTeamSelectedFromEmailInput(inputEmail, expectedTeam) { | ||||
|     const onTeamSelected = expect.createSpy(); | ||||
|     const onTeamSelected = jest.fn(); | ||||
|     doInputEmail(inputEmail, onTeamSelected); | ||||
| 
 | ||||
|     expect(onTeamSelected).toHaveBeenCalledWith(expectedTeam); | ||||
| } | ||||
| 
 | ||||
| function expectSupportFromEmailInput(inputEmail, isSupportShown) { | ||||
|     const onTeamSelected = expect.createSpy(); | ||||
|     const onTeamSelected = jest.fn(); | ||||
|     const res = doInputEmail(inputEmail, onTeamSelected); | ||||
| 
 | ||||
|     expect(res.state.showSupportEmail).toBe(isSupportShown); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import React from 'react'; | ||||
| import ReactTestUtils from 'react-addons-test-utils'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import expect, {createSpy} from 'expect'; | ||||
| import expect from 'expect'; | ||||
| import sinon from 'sinon'; | ||||
| import Promise from 'bluebird'; | ||||
| import * as testUtils from '../../../test-utils'; | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ describe('RoomList', () => { | |||
|         ReactTestUtils.findRenderedComponentWithType(root, RoomList); | ||||
| 
 | ||||
|         movingRoom = createRoom({name: 'Moving room'}); | ||||
|         expect(movingRoom.roomId).toNotBe(null); | ||||
|         expect(movingRoom.roomId).not.toBe(null); | ||||
| 
 | ||||
|         // Mock joined member
 | ||||
|         myMember = new RoomMember(movingRoomId, myUserId); | ||||
|  | @ -139,7 +139,7 @@ describe('RoomList', () => { | |||
|             throw err; | ||||
|         } | ||||
| 
 | ||||
|         expect(expectedRoomTile).toExist(); | ||||
|         expect(expectedRoomTile).toBeTruthy(); | ||||
|         expect(expectedRoomTile.props.room).toBe(room); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import expect, {createSpy} from 'expect'; | ||||
| import expect from 'expect'; | ||||
| import jest from 'jest-mock'; | ||||
| import Promise from 'bluebird'; | ||||
| import * as testUtils from '../../../test-utils'; | ||||
| import sdk from 'matrix-react-sdk'; | ||||
|  | @ -18,12 +19,12 @@ describe('RoomSettings', () => { | |||
| 
 | ||||
|     function expectSentStateEvent(roomId, eventType, expectedEventContent) { | ||||
|         let found = false; | ||||
|         for (const call of client.sendStateEvent.calls) { | ||||
|         for (const call of client.sendStateEvent.mock.calls) { | ||||
|             const [ | ||||
|                 actualRoomId, | ||||
|                 actualEventType, | ||||
|                 actualEventContent, | ||||
|             ] = call.arguments.slice(0, 3); | ||||
|             ] = call.slice(0, 3); | ||||
| 
 | ||||
|             if (roomId === actualRoomId && actualEventType === eventType) { | ||||
|                 expect(actualEventContent).toEqual(expectedEventContent); | ||||
|  | @ -40,20 +41,20 @@ describe('RoomSettings', () => { | |||
|         client = MatrixClientPeg.get(); | ||||
|         client.credentials = {userId: '@me:domain.com'}; | ||||
| 
 | ||||
|         client.setRoomName = createSpy().andReturn(Promise.resolve()); | ||||
|         client.setRoomTopic = createSpy().andReturn(Promise.resolve()); | ||||
|         client.setRoomDirectoryVisibility = createSpy().andReturn(Promise.resolve()); | ||||
|         client.setRoomName = jest.fn().mockReturnValue(Promise.resolve()); | ||||
|         client.setRoomTopic = jest.fn().mockReturnValue(Promise.resolve()); | ||||
|         client.setRoomDirectoryVisibility = jest.fn().mockReturnValue(Promise.resolve()); | ||||
| 
 | ||||
|         // Covers any room state event (e.g. name, avatar, topic)
 | ||||
|         client.sendStateEvent = createSpy().andReturn(Promise.resolve()); | ||||
|         client.sendStateEvent = jest.fn().mockReturnValue(Promise.resolve()); | ||||
| 
 | ||||
|         // Covers room tagging
 | ||||
|         client.setRoomTag = createSpy().andReturn(Promise.resolve()); | ||||
|         client.deleteRoomTag = createSpy().andReturn(Promise.resolve()); | ||||
|         client.setRoomTag = jest.fn().mockReturnValue(Promise.resolve()); | ||||
|         client.deleteRoomTag = jest.fn().mockReturnValue(Promise.resolve()); | ||||
| 
 | ||||
|         // Covers any setting in the SettingsStore
 | ||||
|         // (including local client settings not stored via matrix)
 | ||||
|         SettingsStore.setValue = createSpy().andReturn(Promise.resolve()); | ||||
|         SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve()); | ||||
| 
 | ||||
|         parentDiv = document.createElement('div'); | ||||
|         document.body.appendChild(parentDiv); | ||||
|  | @ -83,9 +84,9 @@ describe('RoomSettings', () => { | |||
| 
 | ||||
|     it('should not set when no setting is changed', (done) => { | ||||
|         roomSettings.save().then(() => { | ||||
|             expect(client.sendStateEvent).toNotHaveBeenCalled(); | ||||
|             expect(client.setRoomTag).toNotHaveBeenCalled(); | ||||
|             expect(client.deleteRoomTag).toNotHaveBeenCalled(); | ||||
|             expect(client.sendStateEvent).not.toHaveBeenCalled(); | ||||
|             expect(client.setRoomTag).not.toHaveBeenCalled(); | ||||
|             expect(client.deleteRoomTag).not.toHaveBeenCalled(); | ||||
|             done(); | ||||
|         }); | ||||
|     }); | ||||
|  | @ -93,7 +94,7 @@ describe('RoomSettings', () => { | |||
|     // XXX: Apparently we do call SettingsStore.setValue
 | ||||
|     xit('should not settings via the SettingsStore when no setting is changed', (done) => { | ||||
|         roomSettings.save().then(() => { | ||||
|             expect(SettingsStore.setValue).toNotHaveBeenCalled(); | ||||
|             expect(SettingsStore.setValue).not.toHaveBeenCalled(); | ||||
|             done(); | ||||
|         }); | ||||
|     }); | ||||
|  | @ -103,7 +104,7 @@ describe('RoomSettings', () => { | |||
|         roomSettings.setName(name); | ||||
| 
 | ||||
|         roomSettings.save().then(() => { | ||||
|             expect(client.setRoomName.calls[0].arguments.slice(0, 2)) | ||||
|             expect(client.setRoomName.mock.calls[0].slice(0, 2)) | ||||
|                 .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]); | ||||
| 
 | ||||
|             done(); | ||||
|  | @ -115,7 +116,7 @@ describe('RoomSettings', () => { | |||
|         roomSettings.setTopic(topic); | ||||
| 
 | ||||
|         roomSettings.save().then(() => { | ||||
|             expect(client.setRoomTopic.calls[0].arguments.slice(0, 2)) | ||||
|             expect(client.setRoomTopic.mock.calls[0].slice(0, 2)) | ||||
|                 .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]); | ||||
| 
 | ||||
|             done(); | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ describe('matrix-to', function() { | |||
|     it('should pick no candidate servers when the room is not found', function() { | ||||
|         peg.get().getRoom = () => null; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -50,7 +50,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -74,7 +74,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(3); | ||||
|         expect(pickedServers[0]).toBe("pl_95"); | ||||
|         // we don't check the 2nd and 3rd servers because that is done by the next test
 | ||||
|  | @ -112,7 +112,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(3); | ||||
|         expect(pickedServers[0]).toBe("first"); | ||||
|         expect(pickedServers[1]).toBe("second"); | ||||
|  | @ -143,7 +143,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(3); | ||||
|         expect(pickedServers[0]).toBe("first"); | ||||
|         expect(pickedServers[1]).toBe("second"); | ||||
|  | @ -178,7 +178,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(3); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -194,7 +194,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -210,7 +210,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -226,7 +226,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -242,7 +242,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -258,7 +258,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(1); | ||||
|         expect(pickedServers[0]).toBe("example.org:8448"); | ||||
|     }); | ||||
|  | @ -292,7 +292,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -325,7 +325,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -358,7 +358,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(1); | ||||
|         expect(pickedServers[0]).toEqual("evilcorp.com"); | ||||
|     }); | ||||
|  | @ -392,7 +392,7 @@ describe('matrix-to', function() { | |||
|             }; | ||||
|         }; | ||||
|         const pickedServers = pickServerCandidates("!somewhere:example.org"); | ||||
|         expect(pickedServers).toExist(); | ||||
|         expect(pickedServers).toBeTruthy(); | ||||
|         expect(pickedServers.length).toBe(1); | ||||
|         expect(pickedServers[0]).toEqual("evilcorp.com"); | ||||
|     }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Travis Ralston
						Travis Ralston