text: "use strict";
  
  import sinon from 'sinon';
  import React from 'react';
  import peg from '../src/MatrixClientPeg';
  import dis from '../src/dispatcher';
  import jssdk from 'matrix-js-sdk';
  import {makeType} from "../src/utils/TypeUtils";
  import {ValidatedServerConfig} from "../src/utils/AutoDiscoveryUtils";
  import ShallowRenderer from 'react-test-renderer/shallow';
  import MatrixClientContext from "../src/contexts/MatrixClientContext";
  const MatrixEvent = jssdk.MatrixEvent;
  
  /**
   * Perform common actions before each test case, e.g. printing the test case
   * name to stdout.
   * @param {Mocha.Context} context  The test context
   */
  export function beforeEach(context) {
      const desc = context.currentTest.fullTitle();
  
      console.log();
  
      // this puts a mark in the chrome devtools timeline, which can help
      // figure out what's been going on.
      if (console.timeStamp) {
          console.timeStamp(desc);
      }
  
      console.log(desc);
      console.log(new Array(1 + desc.length).join("="));
  }
  
  export function getRenderer() {
      // Old: ReactTestUtils.createRenderer();
      return new ShallowRenderer();
  }
  
  /**
   * Stub out the MatrixClient, and configure the MatrixClientPeg object to
   * return it when get() is called.
   *
   * TODO: once the components are updated to get their MatrixClients from
   * the react context, we can get rid of this and just inject a test client
   * via the context instead.
   *
   * @returns {sinon.Sandbox}; remember to call sandbox.restore afterwards.
   */
  export function stubClient() {
      const sandbox = sinon.sandbox.create();
  
      const client = createTestClient();
  
      // stub out the methods in MatrixClientPeg
      //
      // 'sandbox.restore()' doesn't work correctly on inherited methods,
      // so we do this for each method
      const methods = ['get', 'unset', 'replaceUsingCreds'];
      for (let i = 0; i < methods.length; i++) {
          sandbox.stub(peg, methods[i]);
      }
      // MatrixClientPeg.get() is called a /lot/, so implement it with our own
      // fast stub function rather than a sinon stub
      peg.get = function() { return client; };
      return sandbox;
  }
  
  /**
   * Create a stubbed-out MatrixClient
   *
   * @returns {object} MatrixClient stub
   */
  export function createTestClient() {
      return {
          getHomeserverUrl: sinon.stub(),
          getIdentityServerUrl: sinon.stub(),
          getDomain: sinon.stub().returns("matrix.rog"),
          getUserId: sinon.stub().returns("@userId:matrix.rog"),
  
          getPushActionsForEvent: sinon.stub(),
          getRoom: sinon.stub().returns(mkStubRoom()),
          getRooms: sinon.stub().returns([]),
          getVisibleRooms: sinon.stub().returns([]),
          getGroups: sinon.stub().returns([]),
          loginFlows: sinon.stub(),
          on: sinon.stub(),
          removeListener: sinon.stub(),
          isRoomEncrypted: sinon.stub().returns(false),
          peekInRoom: sinon.stub().returns(Promise.resolve(mkStubRoom())),
  
          paginateEventTimeline: sinon.stub().returns(Promise.resolve()),
          sendReadReceipt: sinon.stub().returns(Promise.resolve()),
          getRoomIdForAlias: sinon.stub().returns(Promise.resolve()),
          getRoomDirectoryVisibility: sinon.stub().returns(Promise.resolve()),
          getProfileInfo: sinon.stub().returns(Promise.resolve({})),
          getAccountData: (type) => {
              return mkEvent({
                  type,
                  event: true,
                  content: {},
              });
          },
          mxcUrlToHttp: (mxc) => 'http://this.is.a.url/',
          setAccountData: sinon.stub(),
          sendTyping: sinon.stub().returns(Promise.resolve({})),
          sendMessage: () => Promise.resolve({}),
          getSyncState: () => "SYNCING",
          generateClientSecret: () => "t35tcl1Ent5ECr3T",
          isGuest: () => false,
      };
  }
  
  /**
   * Create an Event.
   * @param {Object} opts Values for the event.
   * @param {string} opts.type The event.type
   * @param {string} opts.room The event.room_id
   * @param {string} opts.user The event.user_id
   * @param {string} opts.skey Optional. The state key (auto inserts empty string)
   * @param {Number} opts.ts   Optional. Timestamp for the event
   * @param {Object} opts.content The event.content
   * @param {boolean} opts.event True to make a MatrixEvent.
   * @return {Object} a JSON object representing this event.
   */
  export function mkEvent(opts) {
      if (!opts.type || !opts.content) {
          throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
      }
      const event = {
          type: opts.type,
          room_id: opts.room,
          sender: opts.user,
          content: opts.content,
          prev_content: opts.prev_content,
          event_id: "$" + Math.random() + "-" + Math.random(),
          origin_server_ts: opts.ts,
      };
      if (opts.skey) {
          event.state_key = opts.skey;
      } else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
           "m.room.power_levels", "m.room.topic",
           "com.example.state"].indexOf(opts.type) !== -1) {
          event.state_key = "";
      }
      return opts.event ? new MatrixEvent(event) : event;
  }
  
  /**
   * Create an m.presence event.
   * @param {Object} opts Values for the presence.
   * @return {Object|MatrixEvent} The event
   */
  export function mkPresence(opts) {
      if (!opts.user) {
          throw new Error("Missing user");
      }
      const event = {
          event_id: "$" + Math.random() + "-" + Math.random(),
          type: "m.presence",
          sender: opts.user,
          content: {
              avatar_url: opts.url,
              displayname: opts.name,
              last_active_ago: opts.ago,
              presence: opts.presence || "offline",
          },
      };
      return opts.event ? new MatrixEvent(event) : event;
  }
  
  /**
   * Create an m.room.member event.
   * @param {Object} opts Values for the membership.
   * @param {string} opts.room The room ID for the event.
   * @param {string} opts.mship The content.membership for the event.
   * @param {string} opts.prevMship The prev_content.membership for the event.
   * @param {string} opts.user The user ID for the event.
   * @param {RoomMember} opts.target The target of the event.
   * @param {string} opts.skey The other user ID for the event if applicable
   * e.g. for invites/bans.
   * @param {string} opts.name The content.displayname for the event.
   * @param {string} opts.url The content.avatar_url for the event.
   * @param {boolean} opts.event True to make a MatrixEvent.
   * @return {Object|MatrixEvent} The event
   */
  export function mkMembership(opts) {
      opts.type = "m.room.member";
      if (!opts.skey) {
          opts.skey = opts.user;
      }
      if (!opts.mship) {
          throw new Error("Missing .mship => " + JSON.stringify(opts));
      }
      opts.content = {
          membership: opts.mship,
      };
      if (opts.prevMship) {
          opts.prev_content = { membership: opts.prevMship };
      }
      if (opts.name) { opts.content.displayname = opts.name; }
      if (opts.url) { opts.content.avatar_url = opts.url; }
      const e = mkEvent(opts);
      if (opts.target) {
          e.target = opts.target;
      }
      return e;
  }
  
  /**
   * Create an m.room.message event.
   * @param {Object} opts Values for the message
   * @param {string} opts.room The room ID for the event.
   * @param {string} opts.user The user ID for the event.
   * @param {string} opts.msg Optional. The content.body for the event.
   * @param {boolean} opts.event True to make a MatrixEvent.
   * @return {Object|MatrixEvent} The event
   */
  export function mkMessage(opts) {
      opts.type = "m.room.message";
      if (!opts.msg) {
          opts.msg = "Random->" + Math.random();
      }
      if (!opts.room || !opts.user) {
          throw new Error("Missing .room or .user from", opts);
      }
      opts.content = {
          msgtype: "m.text",
          body: opts.msg,
      };
      return mkEvent(opts);
  }
  
  export function mkStubRoom(roomId = null) {
      const stubTimeline = { getEvents: () => [] };
      return {
          roomId,
          getReceiptsForEvent: sinon.stub().returns([]),
          getMember: sinon.stub().returns({
              userId: '@member:domain.bla',
              name: 'Member',
              roomId: roomId,
              getAvatarUrl: () => 'mxc://avatar.url/image.png',
          }),
          getMembersWithMembership: sinon.stub().returns([]),
          getJoinedMembers: sinon.stub().returns([]),
          getPendingEvents: () => [],
          getLiveTimeline: () => stubTimeline,
          getUnfilteredTimelineSet: () => null,
          getAccountData: () => null,
          hasMembershipState: () => null,
          getVersion: () => '1',
          shouldUpgradeToVersion: () => null,
          getMyMembership: () => "join",
          maySendMessage: sinon.stub().returns(true),
          currentState: {
              getStateEvents: sinon.stub(),
              mayClientSendStateEvent: sinon.stub().returns(true),
              maySendStateEvent: sinon.stub().returns(true),
              maySendEvent: sinon.stub().returns(true),
              members: [],
          },
          tags: {
              "m.favourite": {
                  order: 0.5,
              },
          },
          setBlacklistUnverifiedDevices: sinon.stub(),
          on: sinon.stub(),
          removeListener: sinon.stub(),
      };
  }
  
  export function mkServerConfig(hsUrl, isUrl) {
      return makeType(ValidatedServerConfig, {
          hsUrl,
          hsName: "TEST_ENVIRONMENT",
          hsNameIsDifferent: false, // yes, we lie
          isUrl,
      });
  }
  
  export function getDispatchForStore(store) {
      // Mock the dispatcher by gut-wrenching. Stores can only __emitChange whilst a
      // dispatcher `_isDispatching` is true.
      return (payload) => {
          dis._isDispatching = true;
          dis._callbacks[store._dispatchToken](payload);
          dis._isDispatching = false;
      };
  }
  
  export function wrapInMatrixClientContext(WrappedComponent) {
      class Wrapper extends React.Component {
          constructor(props) {
              super(props);
  
              this._matrixClient = peg.get();
          }
  
          render() {
              return <MatrixClientContext.Provider value={this._matrixClient}>
                  <WrappedComponent ref={this.props.wrappedRef} {...this.props} />
              </MatrixClientContext.Provider>;
          }
      }
      return Wrapper;
  }
  
  /**
   * Call fn before calling componentDidUpdate on a react component instance, inst.
   * @param {React.Component} inst an instance of a React component.
   * @param {integer} updates Number of updates to wait for. (Defaults to 1.)
   * @returns {Promise} promise that resolves when componentDidUpdate is called on
   *                    given component instance.
   */
  export function waitForUpdate(inst, updates = 1) {
      return new Promise((resolve, reject) => {
          const cdu = inst.componentDidUpdate;
  
          console.log(`Waiting for ${updates} update(s)`);
  
          inst.componentDidUpdate = (prevProps, prevState, snapshot) => {
              updates--;
              console.log(`Got update, ${updates} remaining`);
  
              if (updates == 0) {
                  inst.componentDidUpdate = cdu;
                  resolve();
              }
  
              if (cdu) cdu(prevProps, prevState, snapshot);
          };
      });
  }
  