<commit_before>var maingame;
var map;

window.addEventListener('load', loadResources, false);

function loadResources() {
  // This initializes Akihabara with the default settings.
  // The title (which appears in the browser title bar) is the text we're passing to the function.
  help.akihabaraInit({ width: 640, height: 480, zoom: 1 });

  gbox.addBundle({ file: 'resources/bundle.js?' + timestamp() });

  // The 'main' function is registered as a callback: this just says that when we're done with loadAll we should call 'main'
  gbox.setCallback(main);

  // When everything is ready, the 'loadAll' downloads all the needed resources.
  gbox.loadAll();
};

function main() {
  // For Tutorial Part 3 we're adding 'background' to the next line.
  // The 'background' rendering group that we'll use for our map, and it will render before anything else because we put it first in this list
  gbox.setGroups(['background', 'player', 'game']);

  // Create a new maingame into the "gamecycle" group. Will be called "gamecycle". From now, we've to "override" some of the maingame default actions.
  maingame = gamecycle.createMaingame('game', 'game');

  // Disable the default difficulty-choice menu; we don't need it for our tutorial
  maingame.gameMenu = function() { return true; };

  // Disable the default "get ready" screen; we don't need it for our tutorial
  maingame.gameIntroAnimation = function() { return true; };

  maingame.pressStartIntroAnimation = function() { return true; };

  // Set our intro screen animation
  maingame.gameTitleIntroAnimation = function() { return true; };

  // This function will be called before the game starts running, so here is where we add our game elements
  maingame.initializeGame = function() {
    // Create the 'player' (see tutorial Part 2 for a detailed explanation)
    addPlayer();

    // Here we create a map object that will draw the map onto the 'background' layer each time our game world is drawn
    addMap();
  };

  // Here we define the map, which consists of a tileset, the actual map data, and a helper function for collision
  map = {
    tileset: 'map_pieces', // Specify that we're using the 'map_pieces' tiles that we created in the loadResources function

    // This loads an ASCII-definition of all the 'pieces' of the map as an array of integers specifying a type for each map tile
    // Each 'type' corresponds to a sprite in our tileset. For example, if a map tile has type 0, then it uses the first sprite in the
    //  map's tile set ('map_pieces', as defined above) and if a map tile has type 1, it uses the second sprite in the tile set, etc.
    // Also note that null is an allowed type for a map tile, and uses no sprite from the tile set
    map: loadMap(),

    // This function have to return true if the object 'obj' is checking if the tile 't' is a wall, so...
    tileIsSolidCeil: function(obj, t) {
      if (t != null && t != 3 && t != 5 && t != 6 && t!= 7) return true;
        else return false; // Is a wall if is not an empty space
      },
    tileIsSolidFloor: function(obj, t) {
      if (t != null && t != 3 && t != 5 && t != 6 && t!= 7) return true;
        else return false; // Is a wall if is not an empty space
      }
  }
 //debugger
  // This function calculates the overall height and width of the map and puts them into the 'x' and 'y' fields of the object
  map = help.finalizeTilemap(map);

  // We create a canvas that our map will be drawn to, seting its dimentions by using the map's width and height
  gbox.createCanvas('map_canvas', { w: map.w, h: map.h });

  // We draw the map onto our 'map_canvas' canvas that we created above.
  // This means that the map's 'blit' function can simply draw the 'map_canvas' to the screen to render the map
  gbox.blitTilemap(gbox.getCanvasContext('map_canvas'), map);

  // Now that we've set up our game's elements, this tells the game to run
  gbox.go();
}

// This is our function for adding the player object -- this keeps our main game code nice and clean
function addPlayer() {
  gbox.addObject({
    id:      'player_id',    // id refers to the specific object
    group:   'player',       // The rendering group
    tileset: 'player_tiles', // tileset is where the graphics come from

    // We're overriding the default colh value for the object. "colh" stands for collision height, and it's the height of our collision box. Similarly,
    //  the object automatically has values for colw (collision box width) and colx and coly (the x/y offset of the collision box).
    // We're overriding colh from its default because by default in the toys.topview object, colh is set to half the height of the sprite.
    // We have to do this because topview is normally used for Zelda-style games where the hitbox is considered to be the bottom half
    //  of the sprite so the top half can "overlap" scenery that's "behind" it. In this case we're just setting colh to the default tile height.
    colh: gbox.getTiles('player_tiles').tileh,

    // the initialize function contains code that is run when the object is first created. In the case of the player object this only
    // happens once, at the beginning of the game, or possibly after a player dies and respawns.
    initialize: function() {
      // Toys are helper functions that are specific to certain genres.
      // We're using the "topview" toys because we're creating a game with a top view and Akihabara provides convenient helper functions for that.

      // Here we're just telling it to initialize the object, in this case our player.
      //  The second argument provides data to initialize the object with,
      //  but in this case we don't have any, so we just pass an empty hash
      toys.topview.initialize(this, {});

      // And we set the starting position and jump speed for our player.
      this.x = 20;
      this.y = 20;
      this.jumpaccy = 11;
      this.maxaccx = 7;
    },

    // The 'first' function is like a step function. Tt runs every frame and does calculations. It's called 'first'
    //  because it happens before the rendering, so we calculate new positions and actions and THEN render them
    first: function() {

      if (gbox.keyIsHit("b")) {
        this.x = 20;
        this.y = 20;
      }

      toys.platformer.applyGravity(this); // Apply gravity
					toys.platformer.horizontalKeys(this,{left:"left",right:"right"}); // Moves horizontally
					toys.platformer.verticalTileCollision(this,map,"map",1); // vertical tile collision (i.e. floor)
					toys.platformer.horizontalTileCollision(this,map,"map",1); // horizontal tile collision (i.e. walls)
					toys.platformer.jumpKeys(this,{jump:"a",audiojump:"jump"}); // handle jumping
					toys.platformer.handleAccellerations(this); // gravity/attrito
    },

    // the blit function is what happens during the game's draw cycle. everything related to rendering and drawing goes here
    blit: function() {
      // Render the current sprite.. don't worry too much about what's going on here. We're pretty much doing
      //  the default drawing function, sending along the tileset, the frame info, coordinates, whether the
      //  spries is flipped, camera info, and the alpha transparency value
      gbox.blitTile(gbox.getBufferContext(), {
        tileset: this.tileset,
        tile:    this.frame,
        dx:      this.x,
        dy:      this.y,
        fliph:   this.fliph,
        flipv:   this.flipv,
        camera:  this.camera,
        alpha:   1.0
      });
    },
  });
}<commit_msg>Doubling gravity via toys.platformer.handleAccelerations() override, changing the jump acceleration for the character.<commit_after>var maingame;
var map;

window.addEventListener('load', loadResources, false);

function loadResources() {
  // This initializes Akihabara with the default settings.
  // The title (which appears in the browser title bar) is the text we're passing to the function.
  help.akihabaraInit({ width: 640, height: 480, zoom: 1 });

  gbox.addBundle({ file: 'resources/bundle.js?' + timestamp() });

  // The 'main' function is registered as a callback: this just says that when we're done with loadAll we should call 'main'
  gbox.setCallback(main);

  // When everything is ready, the 'loadAll' downloads all the needed resources.
  gbox.loadAll();
};

function main() {
  // For Tutorial Part 3 we're adding 'background' to the next line.
  // The 'background' rendering group that we'll use for our map, and it will render before anything else because we put it first in this list
  gbox.setGroups(['background', 'player', 'game']);

  // Create a new maingame into the "gamecycle" group. Will be called "gamecycle". From now, we've to "override" some of the maingame default actions.
  maingame = gamecycle.createMaingame('game', 'game');

  // Disable the default difficulty-choice menu; we don't need it for our tutorial
  maingame.gameMenu = function() { return true; };

  // Disable the default "get ready" screen; we don't need it for our tutorial
  maingame.gameIntroAnimation = function() { return true; };

  maingame.pressStartIntroAnimation = function() { return true; };

  // Set our intro screen animation
  maingame.gameTitleIntroAnimation = function() { return true; };

  // This function will be called before the game starts running, so here is where we add our game elements
  maingame.initializeGame = function() {
    // Create the 'player' (see tutorial Part 2 for a detailed explanation)
    addPlayer();

    // Here we create a map object that will draw the map onto the 'background' layer each time our game world is drawn
    addMap();
  };

  // Here we define the map, which consists of a tileset, the actual map data, and a helper function for collision
  map = {
    tileset: 'map_pieces', // Specify that we're using the 'map_pieces' tiles that we created in the loadResources function

    // This loads an ASCII-definition of all the 'pieces' of the map as an array of integers specifying a type for each map tile
    // Each 'type' corresponds to a sprite in our tileset. For example, if a map tile has type 0, then it uses the first sprite in the
    //  map's tile set ('map_pieces', as defined above) and if a map tile has type 1, it uses the second sprite in the tile set, etc.
    // Also note that null is an allowed type for a map tile, and uses no sprite from the tile set
    map: loadMap(),

    // This function have to return true if the object 'obj' is checking if the tile 't' is a wall, so...
    tileIsSolidCeil: function(obj, t) {
      if (t != null && t != 3 && t != 5 && t != 6 && t!= 7) return true;
        else return false; // Is a wall if is not an empty space
      },
    tileIsSolidFloor: function(obj, t) {
      if (t != null && t != 3 && t != 5 && t != 6 && t!= 7) return true;
        else return false; // Is a wall if is not an empty space
      }
  }
 //debugger
  // This function calculates the overall height and width of the map and puts them into the 'x' and 'y' fields of the object
  map = help.finalizeTilemap(map);

  // We create a canvas that our map will be drawn to, seting its dimentions by using the map's width and height
  gbox.createCanvas('map_canvas', { w: map.w, h: map.h });

  // We draw the map onto our 'map_canvas' canvas that we created above.
  // This means that the map's 'blit' function can simply draw the 'map_canvas' to the screen to render the map
  gbox.blitTilemap(gbox.getCanvasContext('map_canvas'), map);

  // Now that we've set up our game's elements, this tells the game to run
  gbox.go();
}

// This is our function for adding the player object -- this keeps our main game code nice and clean
function addPlayer() {
  gbox.addObject({
    id:      'player_id',    // id refers to the specific object
    group:   'player',       // The rendering group
    tileset: 'player_tiles', // tileset is where the graphics come from

    // We're overriding the default colh value for the object. "colh" stands for collision height, and it's the height of our collision box. Similarly,
    //  the object automatically has values for colw (collision box width) and colx and coly (the x/y offset of the collision box).
    // We're overriding colh from its default because by default in the toys.topview object, colh is set to half the height of the sprite.
    // We have to do this because topview is normally used for Zelda-style games where the hitbox is considered to be the bottom half
    //  of the sprite so the top half can "overlap" scenery that's "behind" it. In this case we're just setting colh to the default tile height.
    colh: gbox.getTiles('player_tiles').tileh,

    // the initialize function contains code that is run when the object is first created. In the case of the player object this only
    // happens once, at the beginning of the game, or possibly after a player dies and respawns.
    initialize: function() {
      // Toys are helper functions that are specific to certain genres.
      // We're using the "topview" toys because we're creating a game with a top view and Akihabara provides convenient helper functions for that.

      // Here we're just telling it to initialize the object, in this case our player.
      //  The second argument provides data to initialize the object with,
      //  but in this case we don't have any, so we just pass an empty hash
      toys.topview.initialize(this, {});

      //Overriding the gravity function to double the usual gravity (from 1 to 2).
      toys.platformer.handleAccellerations = function(th) {
			// Gravity
			if (!th.touchedfloor) th.accy += 2;
			// Attrito
			if (th.pushing==toys.PUSH_NONE) if (th.accx) th.accx=help.goToZero(th.accx);
		},

      // And we set the starting position and jump speed for our player.
      this.x = 20;
      this.y = 20;
      this.jumpaccy = 16;
      this.maxaccx = 7;
    },

    // The 'first' function is like a step function. Tt runs every frame and does calculations. It's called 'first'
    //  because it happens before the rendering, so we calculate new positions and actions and THEN render them
    first: function() {

      if (gbox.keyIsHit("b")) {
        this.x = 20;
        this.y = 20;
      }

      toys.platformer.applyGravity(this); // Apply gravity
					toys.platformer.horizontalKeys(this,{left:"left",right:"right"}); // Moves horizontally
					toys.platformer.verticalTileCollision(this,map,"map",1); // vertical tile collision (i.e. floor)
					toys.platformer.horizontalTileCollision(this,map,"map",1); // horizontal tile collision (i.e. walls)
					toys.platformer.jumpKeys(this,{jump:"a",audiojump:"jump"}); // handle jumping
					toys.platformer.handleAccellerations(this); // gravity/attrito
    },

    // the blit function is what happens during the game's draw cycle. everything related to rendering and drawing goes here
    blit: function() {
      // Render the current sprite.. don't worry too much about what's going on here. We're pretty much doing
      //  the default drawing function, sending along the tileset, the frame info, coordinates, whether the
      //  spries is flipped, camera info, and the alpha transparency value
      gbox.blitTile(gbox.getBufferContext(), {
        tileset: this.tileset,
        tile:    this.frame,
        dx:      this.x,
        dy:      this.y,
        fliph:   this.fliph,
        flipv:   this.flipv,
        camera:  this.camera,
        alpha:   1.0
      });
    },
  });
}<|endoftext|>


<commit_before>import _try from 'fast.js/function/try';
import _bind from 'fast.js/function/bind';
import { Random, EJSON } from 'marsdb';
import PromiseQueue from 'marsdb/dist/PromiseQueue';
import AsyncEventEmitter from 'marsdb/dist/AsyncEventEmitter';
import HeartbeatManager from 'marsdb-sync-server/dist/HeartbeatManager';

// Status of a DDP connection
const DDP_VERSION = 1;
const HEARTBEAT_INTERVAL = 17500;
const HEARTBEAT_TIMEOUT = 15000;
const RECONNECT_INTERVAL = 5000;
export const CONN_STATUS = {
  CONNECTING: 'CONNECTING',
  CONNECTED: 'CONNECTED',
  DISCONNECTED: 'DISCONNECTED',
};

export default class DDPConnection extends AsyncEventEmitter {
  constructor({ url, socket = WebSocket, autoReconnect = true }) {
    super();
    this.url = url;
    this._queue = new PromiseQueue(1);
    this._sessionId = null;
    this._autoReconnect = autoReconnect;
    this._socket = socket;
    this._status = CONN_STATUS.DISCONNECTED;

    this._heartbeat = new HeartbeatManager(
      HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT
    );
    this._heartbeat.on('timeout', _bind(this._handleHearbeatTimeout, this));
    this._heartbeat.on('sendPing', _bind(this.sendPing, this));
    this._heartbeat.on('sendPong', _bind(this.sendPong, this));
    this.connect();
  }

  /**
   * Returns true if client is fully connected to a server
   * @return {Boolean}
   */
  get isConnected() {
    return this._status === CONN_STATUS.CONNECTED;
  }

  /**
   * Returns true if client disconnected
   * @return {Boolean}
   */
  get isDisconnected() {
    return this._status === CONN_STATUS.DISCONNECTED;
  }

  /**
   * Sends a "method" message to the server with given
   * parameters
   * @param  {String} name
   * @param  {String} params
   * @param  {String} id
   * @param  {String} randomSeed
   */
  sendMethod(name, params = [], id, randomSeed) {
    const msg = {
      msg: 'method',
      id: id,
      method: name,
      params: params,
    };
    if (randomSeed) {
      msg.randomSeed = randomSeed;
    }
    this._sendMessage(msg);
  }

  /**
   * Send "sub" message to the server with given
   * publusher name and parameters
   * @param  {String} name
   * @param  {Array} params
   * @param  {String} id
   */
  sendSub(name, params = [], id) {
    this._sendMessage({
      msg: 'sub',
      id: id,
      name: name,
      params: params,
    });
  }

  /**
   * Send "unsub" message to the server for given
   * subscription id
   * @param  {String} id
   */
  sendUnsub(id) {
    this._sendMessage({
      msg: 'unsub',
      id: id,
    });
  }

  /**
   * Send a "ping" message with randomly generated ping id
   */
  sendPing() {
    this._sendMessage({
      msg: 'ping',
      id: Random.default().id(20),
    });
  }

  /**
   * Sends a "pong" message for given id of ping message
   * @param  {String} id
   */
  sendPong(id) {
    this._sendMessage({
      msg: 'pong',
      id: id,
    });
  }

  /**
   * Make a new WebSocket connection to the server
   * if we are not connected yet (isDicsonnected).
   * Returns true if connecting, false if already connectiong
   * @returns {Boolean}
   */
  connect() {
    if (this.isDisconnected) {
      this._rawConn = new (this._socket)(this.url);
      this._rawConn.onopen = _bind(this._handleOpen, this);
      this._rawConn.onerror = _bind(this._handleError, this);
      this._rawConn.onclose = _bind(this._handleClose, this);
      this._rawConn.onmessage = _bind(this._handleRawMessage, this);
      this._setStatus(CONN_STATUS.CONNECTING);
      return true;
    }
    return false;
  }

  /**
   * Reconnect to the server with unlimited tries. A period
   * of tries is 5 seconds. It reconnects only if not
   * connected. It cancels previously scheduled `connect` by `reconnect`.
   * Returns a function for canceling reconnection process or undefined
   * if connection is not disconnected.
   * @return {Function}
   */
  reconnect() {
    if (this.isDisconnected) {
      clearTimeout(this._reconnTimer);
      this._reconnecting = true;
      this._reconnTimer = setTimeout(
        _bind(this.connect, this),
        RECONNECT_INTERVAL
      );

      return () => {
        clearTimeout(this._reconnTimer);
        this._reconnecting = false;
        this.disconnect();
      };
    }
  }

  /**
   * Close WebSocket connection. If autoReconnect is enabled
   * (enabled by default), then after 5 sec reconnection will
   * be initiated.
   */
  disconnect() {
    _try(() => this._rawConn && this._rawConn.close());
  }

  _handleOpen() {
    this._heartbeat.waitPing();
    this._sendMessage({
      msg: 'connect',
      session: this._sessionId,
      version: DDP_VERSION,
      support: [DDP_VERSION],
    });
  }

  _handleConnectedMessage(msg) {
    if (!this.isConnected) {
      this._setStatus(CONN_STATUS.CONNECTED, this._reconnecting);
      this._sessionId = msg.session;
      this._reconnecting = false;
    }
  }

  _handleClose() {
    this._heartbeat._clearTimers();
    this._setStatus(CONN_STATUS.DISCONNECTED);

    if (this._autoReconnect) {
      this._reconnecting = false;
      this.reconnect();
    }
  }

  _handleHearbeatTimeout() {
    this.disconnect();
  }

  _handleError(error) {
    this.emit('error', error);
  }

  _handleRawMessage(rawMsg) {
    return this._queue.add(() => {
      const res = _try(() => {
        const msgObj = EJSON.parse(rawMsg.data);
        return this._processMessage(msgObj);
      });
      if (res instanceof Error) {
        return this._handleError(res);
      }
      return res;
    });
  }

  _processMessage(msg) {
    switch (msg.msg) {
      case 'connected': return this._handleConnectedMessage(msg);
      case 'ping': return this._heartbeat.handlePing(msg);
      case 'pong': return this._heartbeat.handlePong(msg);
      case 'removed':
      case 'changed':
      case 'added':
      case 'updated':
      case 'result':
      case 'nosub':
      case 'ready':
      case 'error':
        return this.emitAsync(`message:${msg.msg}`, msg);
      default:
        throw new Error(`Unknown message type ${msg.msg}`);
    }
  }

  _sendMessage(msgObj) {
    const result = _try(() =>
      this._rawConn.send(EJSON.stringify(msgObj))
    );
    if (result instanceof Error) {
      this._handleError(result);
    }
  }

  _setStatus(status, a) {
    this._status = status;
    this.emit(`status:${status}`.toLowerCase(), a);
  }
}
<commit_msg>handle promise rejection while message processing<commit_after>import _try from 'fast.js/function/try';
import _bind from 'fast.js/function/bind';
import { Random, EJSON } from 'marsdb';
import PromiseQueue from 'marsdb/dist/PromiseQueue';
import AsyncEventEmitter from 'marsdb/dist/AsyncEventEmitter';
import HeartbeatManager from 'marsdb-sync-server/dist/HeartbeatManager';

// Status of a DDP connection
const DDP_VERSION = 1;
const HEARTBEAT_INTERVAL = 17500;
const HEARTBEAT_TIMEOUT = 15000;
const RECONNECT_INTERVAL = 5000;
export const CONN_STATUS = {
  CONNECTING: 'CONNECTING',
  CONNECTED: 'CONNECTED',
  DISCONNECTED: 'DISCONNECTED',
};

export default class DDPConnection extends AsyncEventEmitter {
  constructor({ url, socket = WebSocket, autoReconnect = true }) {
    super();
    this.url = url;
    this._queue = new PromiseQueue(1);
    this._sessionId = null;
    this._autoReconnect = autoReconnect;
    this._socket = socket;
    this._status = CONN_STATUS.DISCONNECTED;

    this._heartbeat = new HeartbeatManager(
      HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT
    );
    this._heartbeat.on('timeout', _bind(this._handleHearbeatTimeout, this));
    this._heartbeat.on('sendPing', _bind(this.sendPing, this));
    this._heartbeat.on('sendPong', _bind(this.sendPong, this));
    this.connect();
  }

  /**
   * Returns true if client is fully connected to a server
   * @return {Boolean}
   */
  get isConnected() {
    return this._status === CONN_STATUS.CONNECTED;
  }

  /**
   * Returns true if client disconnected
   * @return {Boolean}
   */
  get isDisconnected() {
    return this._status === CONN_STATUS.DISCONNECTED;
  }

  /**
   * Sends a "method" message to the server with given
   * parameters
   * @param  {String} name
   * @param  {String} params
   * @param  {String} id
   * @param  {String} randomSeed
   */
  sendMethod(name, params = [], id, randomSeed) {
    const msg = {
      msg: 'method',
      id: id,
      method: name,
      params: params,
    };
    if (randomSeed) {
      msg.randomSeed = randomSeed;
    }
    this._sendMessage(msg);
  }

  /**
   * Send "sub" message to the server with given
   * publusher name and parameters
   * @param  {String} name
   * @param  {Array} params
   * @param  {String} id
   */
  sendSub(name, params = [], id) {
    this._sendMessage({
      msg: 'sub',
      id: id,
      name: name,
      params: params,
    });
  }

  /**
   * Send "unsub" message to the server for given
   * subscription id
   * @param  {String} id
   */
  sendUnsub(id) {
    this._sendMessage({
      msg: 'unsub',
      id: id,
    });
  }

  /**
   * Send a "ping" message with randomly generated ping id
   */
  sendPing() {
    this._sendMessage({
      msg: 'ping',
      id: Random.default().id(20),
    });
  }

  /**
   * Sends a "pong" message for given id of ping message
   * @param  {String} id
   */
  sendPong(id) {
    this._sendMessage({
      msg: 'pong',
      id: id,
    });
  }

  /**
   * Make a new WebSocket connection to the server
   * if we are not connected yet (isDicsonnected).
   * Returns true if connecting, false if already connectiong
   * @returns {Boolean}
   */
  connect() {
    if (this.isDisconnected) {
      this._rawConn = new (this._socket)(this.url);
      this._rawConn.onopen = _bind(this._handleOpen, this);
      this._rawConn.onerror = _bind(this._handleError, this);
      this._rawConn.onclose = _bind(this._handleClose, this);
      this._rawConn.onmessage = _bind(this._handleRawMessage, this);
      this._setStatus(CONN_STATUS.CONNECTING);
      return true;
    }
    return false;
  }

  /**
   * Reconnect to the server with unlimited tries. A period
   * of tries is 5 seconds. It reconnects only if not
   * connected. It cancels previously scheduled `connect` by `reconnect`.
   * Returns a function for canceling reconnection process or undefined
   * if connection is not disconnected.
   * @return {Function}
   */
  reconnect() {
    if (this.isDisconnected) {
      clearTimeout(this._reconnTimer);
      this._reconnecting = true;
      this._reconnTimer = setTimeout(
        _bind(this.connect, this),
        RECONNECT_INTERVAL
      );

      return () => {
        clearTimeout(this._reconnTimer);
        this._reconnecting = false;
        this.disconnect();
      };
    }
  }

  /**
   * Close WebSocket connection. If autoReconnect is enabled
   * (enabled by default), then after 5 sec reconnection will
   * be initiated.
   */
  disconnect() {
    _try(() => this._rawConn && this._rawConn.close());
  }

  _handleOpen() {
    this._heartbeat.waitPing();
    this._sendMessage({
      msg: 'connect',
      session: this._sessionId,
      version: DDP_VERSION,
      support: [DDP_VERSION],
    });
  }

  _handleConnectedMessage(msg) {
    if (!this.isConnected) {
      this._setStatus(CONN_STATUS.CONNECTED, this._reconnecting);
      this._sessionId = msg.session;
      this._reconnecting = false;
    }
  }

  _handleClose() {
    this._heartbeat._clearTimers();
    this._setStatus(CONN_STATUS.DISCONNECTED);

    if (this._autoReconnect) {
      this._reconnecting = false;
      this.reconnect();
    }
  }

  _handleHearbeatTimeout() {
    this.disconnect();
  }

  _handleError(error) {
    this.emit('error', error);
  }

  _handleRawMessage(rawMsg) {
    return this._queue.add(() => {
      const res = _try(() => {
        const msgObj = EJSON.parse(rawMsg.data);
        return this._processMessage(msgObj);
      });
      if (res instanceof Error) {
        return this._handleError(res);
      }
      return res;
    }).then(null, err => {
      this._handleError(err);
    });
  }

  _processMessage(msg) {
    switch (msg.msg) {
      case 'connected': return this._handleConnectedMessage(msg);
      case 'ping': return this._heartbeat.handlePing(msg);
      case 'pong': return this._heartbeat.handlePong(msg);
      case 'removed':
      case 'changed':
      case 'added':
      case 'updated':
      case 'result':
      case 'nosub':
      case 'ready':
      case 'error':
        return this.emitAsync(`message:${msg.msg}`, msg);
      default:
        throw new Error(`Unknown message type ${msg.msg}`);
    }
  }

  _sendMessage(msgObj) {
    const result = _try(() =>
      this._rawConn.send(EJSON.stringify(msgObj))
    );
    if (result instanceof Error) {
      this._handleError(result);
    }
  }

  _setStatus(status, a) {
    this._status = status;
    this.emit(`status:${status}`.toLowerCase(), a);
  }
}
<|endoftext|>


<commit_before>import fetchMock from 'fetch-mock';

import NormandyDriver from '../normandy_driver.js';
import { urlPathMatcher } from '../../tests/utils.js';

describe('Normandy Driver', () => {
  describe('showHeartbeat', () => {
    it('should pass all the required arguments to the UITour helper', async () => {
      const uitour = jasmine.createSpyObj('uitour', ['showHeartbeat']);
      const driver = new NormandyDriver(uitour);
      const options = {
        message: 'testMessage',
        thanksMessage: 'testThanks',
        flowId: 'testFlowId',
        postAnswerUrl: 'testPostAnswerUrl',
        learnMoreMessage: 'testLearnMoreMessage',
        learnMoreUrl: 'testLearnMoreUrl',
        engagementButtonLabel: 'testEngagementButtonLabel',
        surveyId: 'testSurveyId',
        surveyVersion: 'testSurveyVersion',
        testing: true,
      };

      await driver.showHeartbeat(options);
      expect(uitour.showHeartbeat).toHaveBeenCalledWith(
        options.message,
        options.thanksMessage,
        options.flowId,
        options.postAnswerUrl,
        options.learnMoreMessage,
        options.learnMoreUrl,
        jasmine.objectContaining({
          engagementButtonLabel: options.engagementButtonLabel,
          surveyId: options.surveyId,
          surveyVersion: options.surveyVersion,
          testing: options.testing,
        }),
      );
    });
  });

  describe('saveHeartbeatFlow', () => {
    const driver = new NormandyDriver();

    afterEach(() => {
      expect(fetchMock.calls().unmatched).toEqual([]);
      fetchMock.restore();
    });

    beforeEach(() => {
      fetchMock.get(urlPathMatcher('/api/v2/hb/'), 200);
    });

    it('should not POST data if in testing mode', async () => {
      driver.testing = true;
      await driver.saveHeartbeatFlow({
        flowId: '123456789',
      });

      expect(fetchMock.calls().matched.length).toEqual(0);
    });

    it('should POST flow data to input', async () => {
      driver.testing = false;
      await driver.saveHeartbeatFlow({
        flowId: '123456789',
      });

      expect(fetchMock.lastOptions()).toEqual({
        method: 'POST',
        body: '{"flowId":"123456789"}',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
      });
    });
  });

  describe('client', () => {
    it("should fetch the user's config from UITour", async () => {
      const uitour = {
        getConfiguration(config, cb) {
          switch (config) {
            case 'sync':
              return cb({
                setup: false,
              });
            case 'appinfo':
              return cb({
                version: '50.0.2',
                defaultUpdateChannel: 'aurora',
                defaultBrowser: true,
                distribution: 'funnelcake85',
              });
            case 'selectedSearchEngine':
              return cb({ searchEngineIdentifier: 'Yahoo' });
            default:
              return cb({});
          }
        },
      };
      const driver = new NormandyDriver(uitour);
      const client = await driver.client();

      expect(client.syncSetup).toEqual(false);
      expect(client.distribution).toEqual('funnelcake85');
      expect(client.isDefaultBrowser).toEqual(true);
      expect(client.searchEngine).toEqual('Yahoo');
    });
  });

  describe('uuid', () => {
    it('returns a valid userID', () => {
      const UUID_ISH_REGEX = /^[a-f0-9-]{36}$/;
      const driver = new NormandyDriver();
      const uuid = driver.uuid();
      expect(UUID_ISH_REGEX.test(uuid)).toBe(true);
    });
  });

  describe('log', () => {
    const driver = new NormandyDriver();

    it('will not log debug level unless in testing mode', () => {
      spyOn(console, 'log');
      driver.testing = false
      driver.log('lorem ipsum');
      expect(console.log.calls.count()).toEqual(0);
    });

    it('will log debug messages in testing mode', () => {
      spyOn(console, 'log');
      driver.testing = true;
      driver.log('lorem ipsum');
      driver.log('lorem ipsum dolor', 'debug');
      expect(console.log.calls.count()).toEqual(2);
    });

    it('will log an error if level is set to error', () => {
      spyOn(console, 'error');
      driver.log('lorem ipsum', 'error');
      expect(console.error.calls.count()).toEqual(1);
    });
  });
});
<commit_msg>Semicolon<commit_after>import fetchMock from 'fetch-mock';

import NormandyDriver from '../normandy_driver.js';
import { urlPathMatcher } from '../../tests/utils.js';

describe('Normandy Driver', () => {
  describe('showHeartbeat', () => {
    it('should pass all the required arguments to the UITour helper', async () => {
      const uitour = jasmine.createSpyObj('uitour', ['showHeartbeat']);
      const driver = new NormandyDriver(uitour);
      const options = {
        message: 'testMessage',
        thanksMessage: 'testThanks',
        flowId: 'testFlowId',
        postAnswerUrl: 'testPostAnswerUrl',
        learnMoreMessage: 'testLearnMoreMessage',
        learnMoreUrl: 'testLearnMoreUrl',
        engagementButtonLabel: 'testEngagementButtonLabel',
        surveyId: 'testSurveyId',
        surveyVersion: 'testSurveyVersion',
        testing: true,
      };

      await driver.showHeartbeat(options);
      expect(uitour.showHeartbeat).toHaveBeenCalledWith(
        options.message,
        options.thanksMessage,
        options.flowId,
        options.postAnswerUrl,
        options.learnMoreMessage,
        options.learnMoreUrl,
        jasmine.objectContaining({
          engagementButtonLabel: options.engagementButtonLabel,
          surveyId: options.surveyId,
          surveyVersion: options.surveyVersion,
          testing: options.testing,
        }),
      );
    });
  });

  describe('saveHeartbeatFlow', () => {
    const driver = new NormandyDriver();

    afterEach(() => {
      expect(fetchMock.calls().unmatched).toEqual([]);
      fetchMock.restore();
    });

    beforeEach(() => {
      fetchMock.get(urlPathMatcher('/api/v2/hb/'), 200);
    });

    it('should not POST data if in testing mode', async () => {
      driver.testing = true;
      await driver.saveHeartbeatFlow({
        flowId: '123456789',
      });

      expect(fetchMock.calls().matched.length).toEqual(0);
    });

    it('should POST flow data to input', async () => {
      driver.testing = false;
      await driver.saveHeartbeatFlow({
        flowId: '123456789',
      });

      expect(fetchMock.lastOptions()).toEqual({
        method: 'POST',
        body: '{"flowId":"123456789"}',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
      });
    });
  });

  describe('client', () => {
    it("should fetch the user's config from UITour", async () => {
      const uitour = {
        getConfiguration(config, cb) {
          switch (config) {
            case 'sync':
              return cb({
                setup: false,
              });
            case 'appinfo':
              return cb({
                version: '50.0.2',
                defaultUpdateChannel: 'aurora',
                defaultBrowser: true,
                distribution: 'funnelcake85',
              });
            case 'selectedSearchEngine':
              return cb({ searchEngineIdentifier: 'Yahoo' });
            default:
              return cb({});
          }
        },
      };
      const driver = new NormandyDriver(uitour);
      const client = await driver.client();

      expect(client.syncSetup).toEqual(false);
      expect(client.distribution).toEqual('funnelcake85');
      expect(client.isDefaultBrowser).toEqual(true);
      expect(client.searchEngine).toEqual('Yahoo');
    });
  });

  describe('uuid', () => {
    it('returns a valid userID', () => {
      const UUID_ISH_REGEX = /^[a-f0-9-]{36}$/;
      const driver = new NormandyDriver();
      const uuid = driver.uuid();
      expect(UUID_ISH_REGEX.test(uuid)).toBe(true);
    });
  });

  describe('log', () => {
    const driver = new NormandyDriver();

    it('will not log debug level unless in testing mode', () => {
      spyOn(console, 'log');
      driver.testing = false;
      driver.log('lorem ipsum');
      expect(console.log.calls.count()).toEqual(0);
    });

    it('will log debug messages in testing mode', () => {
      spyOn(console, 'log');
      driver.testing = true;
      driver.log('lorem ipsum');
      driver.log('lorem ipsum dolor', 'debug');
      expect(console.log.calls.count()).toEqual(2);
    });

    it('will log an error if level is set to error', () => {
      spyOn(console, 'error');
      driver.log('lorem ipsum', 'error');
      expect(console.error.calls.count()).toEqual(1);
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import _ from 'lodash';
import {
    Table
} from 'reactabular';
import Paginator from 'react-pagify';
import segmentize from 'segmentize';
import Spinner from 'components/spinner';

import Debug from 'debug';
let debug = new Debug("components:resttable");

require('react-pagify/style.css');

export default React.createClass({
    propTypes: {
        getData: React.PropTypes.func.isRequired,
        columns: React.PropTypes.array.isRequired
    },
    getInitialState () {
        return {
            loading: false,
            count: 0,
            data: [],
            pagination: {
                page: 0,
                perPage: 100
            }
        };
    },
    componentDidMount () {
        //debug('componentDidMount ', this.props);
        this.onSelectPage(0, this.props);
    },
    componentWillReceiveProps(props){
        //debug('componentWillReceiveProps ', props);
        this.onSelectPage(0, props);
    },
    renderLoading(){
        if(this.state.loading){
            return (<Spinner/>);
        };
    },
    renderError(){
        let {error} = this.state;
        if(!error) return;
        return (

An error occured: {error.name}

Status: {error.statusText}

Status Code: {error.status}

        );
    },
    render () {
        debug('render ', this.state);
        return (

                {this.renderLoading()}
                {this.state.error && this.renderError()}
                {this.renderTable()}

        );
    },
    renderPagination(){
        let {count, pagination} = this.state;
        let pages = Math.ceil(count / pagination.perPage);
        if(pages <= 1){
            return;
        };
        return (

                    <Paginator.Context
                      className="pagify-pagination"
                      segments={segmentize({
                          page: pagination.page,
                          pages: pages,
                          beginPages: 3,
                          endPages: 3,
                          sidePages: 2
                      })}
                      onSelect={this.onSelectPage}>
                      <Paginator.Button page={pagination.page - 1}>Previous</Paginator.Button>

                      <Paginator.Segment field="beginPages" />

                      <Paginator.Ellipsis className="ellipsis"
                        previousField="beginPages" nextField="previousPages" />

                      <Paginator.Segment field="previousPages" />
                      <Paginator.Segment field="centerPage" className="selected" />
                      <Paginator.Segment field="nextPages" />

                      <Paginator.Ellipsis className="ellipsis"
                        previousField="nextPages" nextField="endPages" />

                      <Paginator.Segment field="endPages" />

                      <Paginator.Button page={pagination.page + 1}>Next</Paginator.Button>
                    </Paginator.Context>

        );
    },
    renderTable () {
        let {data} = this.state;
        if(this.state.error) return;
        return (

                {this.renderPagination()}

        );
    },
    onSelectPage(page){
        this.setSelectPage(page, this.props);
    },
    setSelectPage(page, props){
        debug('onSelectPage ', page);
        let {pagination} = this.state;
        this.setState({loading: true});
        let state = {loading: false};
        props.getData({
            offset: pagination.perPage * page,
            limit: pagination.perPage
        })
        .then(result => {
            pagination.page = page;
            _.extend(state, {
                count: result.count,
                data: result.data,
                pagination: pagination
            });
        })
        .catch(err => {
            debug('error: ', err);
            _.extend(state, {error: err});
        })
        .finally(() => {
            if (this.isMounted()) {
                this.setState(state);
            }
        });
    }
});
<commit_msg>finally seems to be broken with es6-promise<commit_after>import React from 'react';
import _ from 'lodash';
import {
    Table
} from 'reactabular';
import Paginator from 'react-pagify';
import segmentize from 'segmentize';
import Spinner from 'components/spinner';

import Debug from 'debug';
let debug = new Debug("components:resttable");

require('react-pagify/style.css');

export default React.createClass({
    propTypes: {
        getData: React.PropTypes.func.isRequired,
        columns: React.PropTypes.array.isRequired
    },
    getInitialState () {
        return {
            loading: false,
            count: 0,
            data: [],
            pagination: {
                page: 0,
                perPage: 100
            }
        };
    },
    componentDidMount () {
        //debug('componentDidMount ', this.props);
        this.onSelectPage(0, this.props);
    },
    componentWillReceiveProps(props){
        //debug('componentWillReceiveProps ', props);
        this.onSelectPage(0, props);
    },
    renderLoading(){
        if(this.state.loading){
            return (<Spinner/>);
        };
    },
    renderError(){
        let {error} = this.state;
        if(!error) return;
        return (

An error occured: {error.name}

Status: {error.statusText}

Status Code: {error.status}

        );
    },
    render () {
        debug('render ', this.state);
        return (

                {this.renderLoading()}
                {this.state.error && this.renderError()}
                {this.renderTable()}

        );
    },
    renderPagination(){
        let {count, pagination} = this.state;
        let pages = Math.ceil(count / pagination.perPage);
        if(pages <= 1){
            return;
        };
        return (

                    <Paginator.Context
                      className="pagify-pagination"
                      segments={segmentize({
                          page: pagination.page,
                          pages: pages,
                          beginPages: 3,
                          endPages: 3,
                          sidePages: 2
                      })}
                      onSelect={this.onSelectPage}>
                      <Paginator.Button page={pagination.page - 1}>Previous</Paginator.Button>

                      <Paginator.Segment field="beginPages" />

                      <Paginator.Ellipsis className="ellipsis"
                        previousField="beginPages" nextField="previousPages" />

                      <Paginator.Segment field="previousPages" />
                      <Paginator.Segment field="centerPage" className="selected" />
                      <Paginator.Segment field="nextPages" />

                      <Paginator.Ellipsis className="ellipsis"
                        previousField="nextPages" nextField="endPages" />

                      <Paginator.Segment field="endPages" />

                      <Paginator.Button page={pagination.page + 1}>Next</Paginator.Button>
                    </Paginator.Context>

        );
    },
    renderTable () {
        let {data} = this.state;
        if(this.state.error) return;
        return (

                {this.renderPagination()}

        );
    },
    onSelectPage(page){
        this.setSelectPage(page, this.props);
    },
    setSelectPage(page, props){
        debug('onSelectPage ', page);
        let {pagination} = this.state;
        this.setState({loading: true});
        props.getData({
            offset: pagination.perPage * page,
            limit: pagination.perPage
        })
        .then(result => {
            pagination.page = page;
            if (this.isMounted()) {
                this.setState({
                    count: result.count,
                    data: result.data,
                    pagination: pagination,
                    loading: false
                });
            }
        })
        .catch(err => {
            debug('error: ', err);
            if (this.isMounted()) {
                this.setState({
                    error: err,
                    loading: false
                });
            }
        });
    }
});
<|endoftext|>


<commit_before>if("undefined"==typeof OGX)var OGX=function(){};OGX.List=function(){function t(t,e){return function(i,r){return i[t]<r[t]?e*-1:i[t]>r[t]?e:0}}function e(t,e,i,r){if(t.hasOwnProperty(e)){var s=t[e];switch(i){case"substr":return s=s.substr(r[0],r[1]);case"numrange":return s=Number(s),Array.isArray(r)?(s=Math.floor(s/r[0]),s="pre"===r[2]?r[1]+s*r[0]+" - "+r[1]+(s+1)*r[0]:s*r[0]+r[1]+" - "+(s+1)*r[0]+r[1]):(s=Math.floor(s/r),s=s*r+" - "+(s+1)*r),s;case"date":if(!Array.isArray(r)){if("year"===r)return s=s.substr(0,4);if("month"===r)return s=s.substr(0,7);if("week"===r)return s=s.substr(0,4)+" week "+moment(s).week()}return s=moment(s).unix(),s=Math.floor(s/(60*r[0]*60*24)),s*=60*r[0]*60*24,s=moment(1e3*s).format(r[1])+" - "+moment(1e3*s+1e3*r[0]*60*60*24).format(r[1])}return s}return!1}if("undefined"==typeof moment)return void console.log("%c CRITICAL ERROR - moment.js not found! ","background:#222; color: #FF0000");1===arguments.length&&arguments[0]instanceof Array&&(arguments=arguments[0]);var i=Object.create(Array.prototype);i=Array.apply(i,arguments)||i;for(var r in OGX.List.prototype)OGX.List.prototype.hasOwnProperty(r)&&(i[r]=OGX.List.prototype[r]);return i.insert=function(t,e){i.splice(e,0,t)},i.delete=function(t){i.splice(t,1)},i.findDelete=function(t,e,i){var r=0;"undefined"==typeof i&&(i=0);for(var s=[],n=0;n<this.length;n++)if(this[n][t]===e){if(s.push(this.splice(n,1)),r++,i>0&&i===r)return s;n--}return s},i.findReplace=function(t,e,i,r){var s=0;"undefined"==typeof r&&(r=0);for(var n=[],h=0;h<this.length;h++)if(this[h][t]===e&&(n.push(this[h]),s++,this[h]=i,r>0&&r===s))return n;return n},i.findUpdate=function(t,e,i,r,s){var n=0;"undefined"==typeof r&&(r=!1),"undefined"==typeof s&&(s=0);for(var h,o=[],a=0;a<this.length;a++)if(this[a][t]===e){h=!1;for(var f in i)r?this[a].hasOwnProperty(f)&&(this[a][f]=i[f],h=!0,n++):(this[a][f]=i[f],h=!0,n++);if(h&&(o.push(this[a]),s>0&&s===n))return o}return o},i.findIndex=function(t,e){var i=!1;e instanceof RegExp&&(i=!0);for(var r=0;r<this.length;r++)if(i){if(this[r][t].match(e))return{index:r}}else if(this[r][t]===e)return{index:r};return!1},i.find=function(t,e,i){var r=0,s=[],n=!1;e instanceof RegExp&&(n=!0);for(var h=0;h<this.length;h++)if(n?this[h][t].match(e)&&(s.push(this[h]),r++):this[h][t]===e&&(s.push(this[h]),r++),i>0&&r===i)return new OGX.List(s);return new OGX.List(s)},i.order=function(e,r){i.sort(t(e,r))},i.group=function(t,i,r){for(var s,n={},h=0;h<this.length;h++)s=e(this[h],t,i,r),s&&(n.hasOwnProperty(s)||(n[s]=[]),n[s].push({item:this[h],pos:h}));return n},i.filters={},i.addFilter=function(t,e,i){return!this.filters.hasOwnProperty(t)&&(this.filters[t]={value:i,mode:e},!0)},i.removeFilter=function(t){return!!this.filters.hasOwnProperty(t)&&(delete this.filters[t],!0)},i.getFilters=function(){return this.filters},i.resetFilters=function(){this.filters={}},i.setFilters=function(t){this.filters=t},i.filter=function(){for(var t,e=[],r=!1,s=/[0-9]{4}\-[0-9]{2}\-[0-9]{2}/g,n=!1,h=0;h<this.length;h++){n=!0;for(var o in this.filters)if(this[h].hasOwnProperty(o)){switch(r=!0,t=!1,isString=!1,Array.isArray(this[h][o])?"string"==typeof this[h][o][0]&&(isString=!0,this[h][o][0].match(s)&&(t=!0)):"string"==typeof this[h][o]&&(isString=!0,this[h][o].match(s)&&(t=!0)),this.filters[o].mode){case"eq":isString?this[h][o].toLowerCase()!==this.filters[o].value.toLowerCase()&&(n=!1):this[h][o]!==this.filters[o].value&&(n=!1);break;case"eqjson":JSON.stringify(this[h][o])!==JSON.stringify(this.filters[o].value)&&(n=!1);break;case"neq":isString?this[h][o].toLowerCase()===this.filters[o].value.toLowerCase()&&(n=!1):this[h][o]===this.filters[o].value&&(n=!1);break;case"in":isString?this[h][o].toLowerCase().indexOf(this.filters[o].value.toLowerCase())===-1&&(n=!1):this[h][o].indexOf(this.filters[o].value)===-1&&(n=!1);break;case"nin":isString?this[h][o].toLowerCase().indexOf(this.filters[o].value.toLowerCase())!==-1&&(n=!1):this[h][o].indexOf(this.filters[o].value)!==-1&&(n=!1);break;case"gt":t?moment(this[h][o]).unix()<=moment(this.filters[o].value).unix()&&(n=!1):this[h][o]<=this.filters[o].value&&(n=!1);break;case"gte":t?moment(this[h][o]).unix()<moment(this.filters[o].value).unix()&&(n=!1):this[h][o]<this.filters[o].value&&(n=!1);break;case"lt":t?moment(this[h][o]).unix()>=moment(this.filters[o].value).unix()&&(n=!1):this[h][o]>=this.filters[o].value&&(n=!1);break;case"lte":t?moment(this[h][o]).unix()>moment(this.filters[o].value).unix()&&(n=!1):this[h][o]>this.filters[o].value&&(n=!0);break;case"btw":t?(moment(this.filters[o].value[0]).unix()>moment(this[h][o]).unix()||moment(this.filters[o].value[1]).unix()<moment(this[h][o]).unix())&&(n=!1):(this.filters[o].value[0]>this[h][o]||this.filters[o].value[1]<this[h][o])&&(n=!1);break;case"substr":this[h][o].toLowerCase().substr(this.filters[o].value[0],this.filters[o].value[1]).toLowerCase()!==this.filters[o].value[2].toLowerCase()&&(n=!1);break;case"regex":this[h][o].match(this.filters[o].value)||(n=!1)}if(!n)break}n&&e.push(this[h])}return r?OGX.List.apply(null,e):i},i};<commit_msg>Fixed findDelete not returning proper array<commit_after>if("undefined"==typeof OGX)var OGX=function(){};OGX.List=function(){function t(t,e){return function(i,r){return i[t]<r[t]?e*-1:i[t]>r[t]?e:0}}function e(t,e,i,r){if(t.hasOwnProperty(e)){var s=t[e];switch(i){case"substr":return s=s.substr(r[0],r[1]);case"numrange":return s=Number(s),Array.isArray(r)?(s=Math.floor(s/r[0]),s="pre"===r[2]?r[1]+s*r[0]+" - "+r[1]+(s+1)*r[0]:s*r[0]+r[1]+" - "+(s+1)*r[0]+r[1]):(s=Math.floor(s/r),s=s*r+" - "+(s+1)*r),s;case"date":if(!Array.isArray(r)){if("year"===r)return s=s.substr(0,4);if("month"===r)return s=s.substr(0,7);if("week"===r)return s=s.substr(0,4)+" week "+moment(s).week()}return s=moment(s).unix(),s=Math.floor(s/(60*r[0]*60*24)),s*=60*r[0]*60*24,s=moment(1e3*s).format(r[1])+" - "+moment(1e3*s+1e3*r[0]*60*60*24).format(r[1])}return s}return!1}if("undefined"==typeof moment)return void console.log("%c CRITICAL ERROR - moment.js not found! ","background:#222; color: #FF0000");1===arguments.length&&arguments[0]instanceof Array&&(arguments=arguments[0]);var i=Object.create(Array.prototype);i=Array.apply(i,arguments)||i;for(var r in OGX.List.prototype)OGX.List.prototype.hasOwnProperty(r)&&(i[r]=OGX.List.prototype[r]);return i.insert=function(t,e){i.splice(e,0,t)},i.delete=function(t){i.splice(t,1)},i.findDelete=function(t,e,i){var r=0;"undefined"==typeof i&&(i=0);for(var s=[],n=0;n<this.length;n++)if(this[n][t]===e){if(s.push(this.splice(n,1)[0]),r++,i>0&&i===r)return s;n--}return s},i.findReplace=function(t,e,i,r){var s=0;"undefined"==typeof r&&(r=0);for(var n=[],h=0;h<this.length;h++)if(this[h][t]===e&&(n.push(this[h]),s++,this[h]=i,r>0&&r===s))return n;return n},i.findUpdate=function(t,e,i,r,s){var n=0;"undefined"==typeof r&&(r=!1),"undefined"==typeof s&&(s=0);for(var h,o=[],a=0;a<this.length;a++)if(this[a][t]===e){h=!1;for(var f in i)r?this[a].hasOwnProperty(f)&&(this[a][f]=i[f],h=!0,n++):(this[a][f]=i[f],h=!0,n++);if(h&&(o.push(this[a]),s>0&&s===n))return o}return o},i.findIndex=function(t,e){var i=!1;e instanceof RegExp&&(i=!0);for(var r=0;r<this.length;r++)if(i){if(this[r][t].match(e))return{index:r}}else if(this[r][t]===e)return{index:r};return!1},i.find=function(t,e,i){var r=0,s=[],n=!1;e instanceof RegExp&&(n=!0);for(var h=0;h<this.length;h++)if(n?this[h][t].match(e)&&(s.push(this[h]),r++):this[h][t]===e&&(s.push(this[h]),r++),i>0&&r===i)return new OGX.List(s);return new OGX.List(s)},i.order=function(e,r){i.sort(t(e,r))},i.group=function(t,i,r){for(var s,n={},h=0;h<this.length;h++)s=e(this[h],t,i,r),s&&(n.hasOwnProperty(s)||(n[s]=[]),n[s].push({item:this[h],pos:h}));return n},i.filters={},i.addFilter=function(t,e,i){return!this.filters.hasOwnProperty(t)&&(this.filters[t]={value:i,mode:e},!0)},i.removeFilter=function(t){return!!this.filters.hasOwnProperty(t)&&(delete this.filters[t],!0)},i.getFilters=function(){return this.filters},i.resetFilters=function(){this.filters={}},i.setFilters=function(t){this.filters=t},i.filter=function(){for(var t,e=[],r=!1,s=/[0-9]{4}\-[0-9]{2}\-[0-9]{2}/g,n=!1,h=0;h<this.length;h++){n=!0;for(var o in this.filters)if(this[h].hasOwnProperty(o)){switch(r=!0,t=!1,isString=!1,Array.isArray(this[h][o])?"string"==typeof this[h][o][0]&&(isString=!0,this[h][o][0].match(s)&&(t=!0)):"string"==typeof this[h][o]&&(isString=!0,this[h][o].match(s)&&(t=!0)),this.filters[o].mode){case"eq":isString?this[h][o].toLowerCase()!==this.filters[o].value.toLowerCase()&&(n=!1):this[h][o]!==this.filters[o].value&&(n=!1);break;case"eqjson":JSON.stringify(this[h][o])!==JSON.stringify(this.filters[o].value)&&(n=!1);break;case"neq":isString?this[h][o].toLowerCase()===this.filters[o].value.toLowerCase()&&(n=!1):this[h][o]===this.filters[o].value&&(n=!1);break;case"in":isString?this[h][o].toLowerCase().indexOf(this.filters[o].value.toLowerCase())===-1&&(n=!1):this[h][o].indexOf(this.filters[o].value)===-1&&(n=!1);break;case"nin":isString?this[h][o].toLowerCase().indexOf(this.filters[o].value.toLowerCase())!==-1&&(n=!1):this[h][o].indexOf(this.filters[o].value)!==-1&&(n=!1);break;case"gt":t?moment(this[h][o]).unix()<=moment(this.filters[o].value).unix()&&(n=!1):this[h][o]<=this.filters[o].value&&(n=!1);break;case"gte":t?moment(this[h][o]).unix()<moment(this.filters[o].value).unix()&&(n=!1):this[h][o]<this.filters[o].value&&(n=!1);break;case"lt":t?moment(this[h][o]).unix()>=moment(this.filters[o].value).unix()&&(n=!1):this[h][o]>=this.filters[o].value&&(n=!1);break;case"lte":t?moment(this[h][o]).unix()>moment(this.filters[o].value).unix()&&(n=!1):this[h][o]>this.filters[o].value&&(n=!0);break;case"btw":t?(moment(this.filters[o].value[0]).unix()>moment(this[h][o]).unix()||moment(this.filters[o].value[1]).unix()<moment(this[h][o]).unix())&&(n=!1):(this.filters[o].value[0]>this[h][o]||this.filters[o].value[1]<this[h][o])&&(n=!1);break;case"substr":this[h][o].toLowerCase().substr(this.filters[o].value[0],this.filters[o].value[1]).toLowerCase()!==this.filters[o].value[2].toLowerCase()&&(n=!1);break;case"regex":this[h][o].match(this.filters[o].value)||(n=!1)}if(!n)break}n&&e.push(this[h])}return r?OGX.List.apply(null,e):i},i};<|endoftext|>


<commit_before>import TestUtils, { act } from 'react-dom/test-utils';
import CSSMotion from 'rc-motion';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import Modal from '..';
import { destroyFns } from '../Modal';
import { sleep } from '../../../tests/utils';

const { confirm } = Modal;

jest.mock('rc-motion');

describe('Modal.confirm triggers callbacks correctly', () => {
  // Inject CSSMotion to replace with No transition support
  const MockCSSMotion = genCSSMotion(false);
  Object.keys(MockCSSMotion).forEach(key => {
    CSSMotion[key] = MockCSSMotion[key];
  });

  // Mock for rc-util raf
  window.requestAnimationFrame = callback => window.setTimeout(callback, 16);
  window.cancelAnimationFrame = id => {
    window.clearTimeout(id);
  };

  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

  /* eslint-disable no-console */
  // Hack error to remove act warning
  const originError = console.error;
  console.error = (...args) => {
    const errorStr = String(args[0]);
    if (errorStr.includes('was not wrapped in act(...)')) {
      return;
    }

    originError(...args);
  };
  /* eslint-enable */

  afterEach(() => {
    errorSpy.mockReset();
    document.body.innerHTML = '';
    Modal.destroyAll();
  });

  afterAll(() => {
    errorSpy.mockRestore();
  });

  function $$(className) {
    return document.body.querySelectorAll(className);
  }

  function open(args) {
    jest.useFakeTimers();
    confirm({
      title: 'Want to delete these items?',
      content: 'some descriptions',
      ...args,
    });
    jest.runAllTimers();
    jest.useRealTimers();
  }

  it('should not render title when title not defined', () => {
    jest.useFakeTimers();
    confirm({
      content: 'some descriptions',
    });
    jest.runAllTimers();
    expect(document.querySelector('.ant-modal-confirm-title')).toBe(null);
    jest.useRealTimers();
  });

  it('trigger onCancel once when click on cancel button', () => {
    const onCancel = jest.fn();
    const onOk = jest.fn();
    open({
      onCancel,
      onOk,
    });
    // first Modal
    $$('.ant-btn')[0].click();
    expect(onCancel.mock.calls.length).toBe(1);
    expect(onOk.mock.calls.length).toBe(0);
  });

  it('trigger onOk once when click on ok button', () => {
    const onCancel = jest.fn();
    const onOk = jest.fn();
    open({
      onCancel,
      onOk,
    });
    // second Modal
    $$('.ant-btn-primary')[0].click();
    expect(onCancel.mock.calls.length).toBe(0);
    expect(onOk.mock.calls.length).toBe(1);
  });

  it('should allow Modal.confirm without onCancel been set', () => {
    open();
    // Third Modal
    $$('.ant-btn')[0].click();
    expect(errorSpy).not.toHaveBeenCalled();
  });

  it('should allow Modal.confirm without onOk been set', () => {
    open();
    // Fourth Modal
    $$('.ant-btn-primary')[0].click();
    expect(errorSpy).not.toHaveBeenCalled();
  });

  it('should close confirm modal when press ESC', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      onCancel,
    });
    jest.runAllTimers();
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
    TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
      keyCode: KeyCode.ESC,
    });
    jest.runAllTimers();
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should not hide confirm when onOk return Promise.resolve', () => {
    open({
      onOk: () => Promise.resolve(''),
    });
    $$('.ant-btn-primary')[0].click();
    expect($$('.ant-modal-confirm')).toHaveLength(1);
  });

  it('should emit error when onOk return Promise.reject', async () => {
    const error = new Error('something wrong');
    open({
      onOk: () => Promise.reject(error),
    });
    $$('.ant-btn-primary')[0].click();

    // wait promise
    await sleep();

    expect(errorSpy).toHaveBeenCalledWith(error);
  });

  it('shows animation when close', () => {
    open();
    jest.useFakeTimers();
    expect($$('.ant-modal-confirm')).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$('.ant-modal-confirm')).toHaveLength(0);
    jest.useRealTimers();
  });

  it('ok only', () => {
    open({ okCancel: false });
    expect($$('.ant-btn')).toHaveLength(1);
    expect($$('.ant-btn')[0].innerHTML).toContain('OK');
  });

  it('allows extra props on buttons', () => {
    open({ okButtonProps: { disabled: true }, cancelButtonProps: { 'data-test': 'baz' } });
    expect($$('.ant-btn')).toHaveLength(2);
    expect($$('.ant-btn')[0].attributes['data-test'].value).toBe('baz');
    expect($$('.ant-btn')[1].disabled).toBe(true);
  });

  it('should close modals when click confirm button', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      $$('.ant-btn')[0].click();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('should close confirm modal when click cancel button', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      onCancel,
    });
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should close confirm modal when click close button', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      closable: true,
      closeIcon: 'X',
      onCancel,
    });
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-close`)).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-close`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should not close modals when click confirm button when onOk has argument', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
        onOk: close => null, // eslint-disable-line no-unused-vars
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      $$('.ant-btn')[0].click();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
    });
    jest.useRealTimers();
  });

  it('could be update by new config', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('content');
      instance.update({
        title: 'new title',
        content: 'new content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('new title');
      expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('new content');
      instance.destroy();
      jest.runAllTimers();
    });
    jest.useRealTimers();
  });

  it('could be update by call function', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        okButtonProps: {
          loading: true,
          style: {
            color: 'red',
          },
        },
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).toContain(
        'ant-btn-loading',
      );
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
      instance.update(prevConfig => ({
        ...prevConfig,
        okButtonProps: {
          ...prevConfig.okButtonProps,
          loading: false,
        },
      }));
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).not.toContain(
        'ant-btn-loading',
      );
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
      instance.destroy();
      jest.runAllTimers();
    });
    jest.useRealTimers();
  });

  it('could be destroy', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      instance.destroy();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('could be Modal.destroyAll', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
    });
    Modal.destroyAll();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('prefixCls', () => {
    open({ prefixCls: 'custom-modal' });
    expect($$('.custom-modal-mask')).toHaveLength(1);
    expect($$('.custom-modal-wrap')).toHaveLength(1);
    expect($$('.custom-modal-confirm')).toHaveLength(1);
    expect($$('.custom-modal-confirm-body-wrapper')).toHaveLength(1);
  });

  it('should be Modal.confirm without mask', () => {
    open({ mask: false });
    expect($$('.ant-modal-mask')).toHaveLength(0);
  });

  it('destroyFns should reduce when instance.destroy', () => {
    jest.useFakeTimers();

    Modal.destroyAll(); // clear destroyFns
    jest.runAllTimers();

    const instances = [];
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });

      // Render modal
      act(() => {
        jest.runAllTimers();
      });

      instances.push(instance);
    });
    const { length } = instances;
    instances.forEach((instance, index) => {
      expect(destroyFns.length).toBe(length - index);

      act(() => {
        instance.destroy();
        jest.runAllTimers();
      });
      expect(destroyFns.length).toBe(length - index - 1);
    });

    jest.useRealTimers();
  });

  it('should warning when pass a string as icon props', () => {
    jest.useFakeTimers();
    const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    confirm({
      content: 'some descriptions',
      icon: 'ab',
    });
    jest.runAllTimers();
    expect(warnSpy).not.toHaveBeenCalled();
    confirm({
      content: 'some descriptions',
      icon: 'question',
    });
    jest.runAllTimers();
    expect(warnSpy).toHaveBeenCalledWith(
      `Warning: [antd: Modal] \`icon\` is using ReactNode instead of string naming in v4. Please check \`question\` at https://ant.design/components/icon`,
    );
    warnSpy.mockRestore();
    jest.useRealTimers();
  });

  it('ok button should trigger onOk once when click it many times quickly', () => {
    const onOk = jest.fn();
    open({ onOk });
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    expect(onOk).toHaveBeenCalledTimes(1);
  });

  // https://github.com/ant-design/ant-design/issues/23358
  it('ok button should trigger onOk multiple times when onOk has close argument', () => {
    const onOk = jest.fn();
    open({
      onOk: close => {
        onOk();
        (() => {})(close); // do nothing
      },
    });
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    expect(onOk).toHaveBeenCalledTimes(3);
  });

  it('should be able to config rootPrefixCls', () => {
    jest.useFakeTimers();
    Modal.config({
      rootPrefixCls: 'my',
    });
    confirm({
      title: 'title',
    });
    jest.runAllTimers();
    expect(document.querySelectorAll('.ant-btn').length).toBe(0);
    expect(document.querySelectorAll('.my-btn').length).toBe(2);
    expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
    Modal.config({
      rootPrefixCls: 'your',
    });
    confirm({
      title: 'title',
    });
    jest.runAllTimers();
    expect(document.querySelectorAll('.ant-btn').length).toBe(0);
    expect(document.querySelectorAll('.my-btn').length).toBe(2);
    expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
    expect(document.querySelectorAll('.your-btn').length).toBe(2);
    expect(document.querySelectorAll('.your-modal-confirm').length).toBe(1);
    Modal.config({
      rootPrefixCls: 'ant',
    });
    jest.useRealTimers();
  });

  it('trigger afterClose once when click on cancel button', () => {
    const afterClose = jest.fn();
    open({
      afterClose,
    });
    // first Modal
    $$('.ant-btn')[0].click();
    expect(afterClose.mock.calls.length).toBe(0);
  });

  it('trigger afterClose once when click on ok button', () => {
    const afterClose = jest.fn();
    open({
      afterClose,
    });
    // second Modal
    $$('.ant-btn-primary')[0].click();
    expect(afterClose.mock.calls.length).toBe(0);
  });
});
<commit_msg>test(✅): fix Modal coverage (#28402)<commit_after>import TestUtils, { act } from 'react-dom/test-utils';
import CSSMotion from 'rc-motion';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import Modal from '..';
import { destroyFns } from '../Modal';
import { sleep } from '../../../tests/utils';

const { confirm } = Modal;

jest.mock('rc-motion');

describe('Modal.confirm triggers callbacks correctly', () => {
  // Inject CSSMotion to replace with No transition support
  const MockCSSMotion = genCSSMotion(false);
  Object.keys(MockCSSMotion).forEach(key => {
    CSSMotion[key] = MockCSSMotion[key];
  });

  // Mock for rc-util raf
  window.requestAnimationFrame = callback => window.setTimeout(callback, 16);
  window.cancelAnimationFrame = id => {
    window.clearTimeout(id);
  };

  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

  /* eslint-disable no-console */
  // Hack error to remove act warning
  const originError = console.error;
  console.error = (...args) => {
    const errorStr = String(args[0]);
    if (errorStr.includes('was not wrapped in act(...)')) {
      return;
    }

    originError(...args);
  };
  /* eslint-enable */

  afterEach(() => {
    errorSpy.mockReset();
    document.body.innerHTML = '';
    Modal.destroyAll();
  });

  afterAll(() => {
    errorSpy.mockRestore();
  });

  function $$(className) {
    return document.body.querySelectorAll(className);
  }

  function open(args) {
    jest.useFakeTimers();
    confirm({
      title: 'Want to delete these items?',
      content: 'some descriptions',
      ...args,
    });
    jest.runAllTimers();
    jest.useRealTimers();
  }

  it('should not render title when title not defined', () => {
    jest.useFakeTimers();
    confirm({
      content: 'some descriptions',
    });
    jest.runAllTimers();
    expect(document.querySelector('.ant-modal-confirm-title')).toBe(null);
    jest.useRealTimers();
  });

  it('trigger onCancel once when click on cancel button', () => {
    const onCancel = jest.fn();
    const onOk = jest.fn();
    open({
      onCancel,
      onOk,
    });
    // first Modal
    $$('.ant-btn')[0].click();
    expect(onCancel.mock.calls.length).toBe(1);
    expect(onOk.mock.calls.length).toBe(0);
  });

  it('trigger onOk once when click on ok button', () => {
    const onCancel = jest.fn();
    const onOk = jest.fn();
    open({
      onCancel,
      onOk,
    });
    // second Modal
    $$('.ant-btn-primary')[0].click();
    expect(onCancel.mock.calls.length).toBe(0);
    expect(onOk.mock.calls.length).toBe(1);
  });

  it('should allow Modal.confirm without onCancel been set', () => {
    open();
    // Third Modal
    $$('.ant-btn')[0].click();
    expect(errorSpy).not.toHaveBeenCalled();
  });

  it('should allow Modal.confirm without onOk been set', () => {
    open();
    // Fourth Modal
    $$('.ant-btn-primary')[0].click();
    expect(errorSpy).not.toHaveBeenCalled();
  });

  it('should close confirm modal when press ESC', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      onCancel,
    });
    jest.runAllTimers();
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
    TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
      keyCode: KeyCode.ESC,
    });
    jest.runAllTimers();
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should not hide confirm when onOk return Promise.resolve', () => {
    open({
      onOk: () => Promise.resolve(''),
    });
    $$('.ant-btn-primary')[0].click();
    expect($$('.ant-modal-confirm')).toHaveLength(1);
  });

  it('should emit error when onOk return Promise.reject', async () => {
    const error = new Error('something wrong');
    open({
      onOk: () => Promise.reject(error),
    });
    $$('.ant-btn-primary')[0].click();

    // wait promise
    await sleep();

    expect(errorSpy).toHaveBeenCalledWith(error);
  });

  it('shows animation when close', () => {
    open();
    jest.useFakeTimers();
    expect($$('.ant-modal-confirm')).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$('.ant-modal-confirm')).toHaveLength(0);
    jest.useRealTimers();
  });

  it('ok only', () => {
    open({ okCancel: false });
    expect($$('.ant-btn')).toHaveLength(1);
    expect($$('.ant-btn')[0].innerHTML).toContain('OK');
  });

  it('allows extra props on buttons', () => {
    open({ okButtonProps: { disabled: true }, cancelButtonProps: { 'data-test': 'baz' } });
    expect($$('.ant-btn')).toHaveLength(2);
    expect($$('.ant-btn')[0].attributes['data-test'].value).toBe('baz');
    expect($$('.ant-btn')[1].disabled).toBe(true);
  });

  it('should close modals when click confirm button', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      $$('.ant-btn')[0].click();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('should close confirm modal when click cancel button', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      onCancel,
    });
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should close confirm modal when click close button', () => {
    jest.useFakeTimers();
    const onCancel = jest.fn();
    Modal.confirm({
      title: 'title',
      content: 'content',
      closable: true,
      closeIcon: 'X',
      onCancel,
    });
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-close`)).toHaveLength(1);
    $$('.ant-btn')[0].click();
    act(() => {
      jest.runAllTimers();
    });
    expect($$(`.ant-modal-close`)).toHaveLength(0);
    expect(onCancel).toHaveBeenCalledTimes(1);
    jest.useRealTimers();
  });

  it('should not close modals when click confirm button when onOk has argument', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
        onOk: close => null, // eslint-disable-line no-unused-vars
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      $$('.ant-btn')[0].click();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
    });
    jest.useRealTimers();
  });

  it('could be update by new config', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('content');
      instance.update({
        title: 'new title',
        content: 'new content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('new title');
      expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('new content');
      instance.destroy();
      jest.runAllTimers();
    });
    jest.useRealTimers();
  });

  it('could be update by call function', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        okButtonProps: {
          loading: true,
          style: {
            color: 'red',
          },
        },
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).toContain(
        'ant-btn-loading',
      );
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
      instance.update(prevConfig => ({
        ...prevConfig,
        okButtonProps: {
          ...prevConfig.okButtonProps,
          loading: false,
        },
      }));
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).not.toContain(
        'ant-btn-loading',
      );
      expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
      instance.destroy();
      jest.runAllTimers();
    });
    jest.useRealTimers();
  });

  it('could be destroy', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
      instance.destroy();
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('could be Modal.destroyAll', () => {
    jest.useFakeTimers();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      Modal[type]({
        title: 'title',
        content: 'content',
      });
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
    });
    Modal.destroyAll();
    ['info', 'success', 'warning', 'error'].forEach(type => {
      act(() => {
        jest.runAllTimers();
      });
      expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
    });
    jest.useRealTimers();
  });

  it('prefixCls', () => {
    open({ prefixCls: 'custom-modal' });
    expect($$('.custom-modal-mask')).toHaveLength(1);
    expect($$('.custom-modal-wrap')).toHaveLength(1);
    expect($$('.custom-modal-confirm')).toHaveLength(1);
    expect($$('.custom-modal-confirm-body-wrapper')).toHaveLength(1);
  });

  it('should be Modal.confirm without mask', () => {
    open({ mask: false });
    expect($$('.ant-modal-mask')).toHaveLength(0);
  });

  it('destroyFns should reduce when instance.destroy', () => {
    jest.useFakeTimers();

    Modal.destroyAll(); // clear destroyFns
    jest.runAllTimers();

    const instances = [];
    ['info', 'success', 'warning', 'error'].forEach(type => {
      const instance = Modal[type]({
        title: 'title',
        content: 'content',
      });

      // Render modal
      act(() => {
        jest.runAllTimers();
      });

      instances.push(instance);
    });
    const { length } = instances;
    instances.forEach((instance, index) => {
      expect(destroyFns.length).toBe(length - index);

      act(() => {
        instance.destroy();
        jest.runAllTimers();
      });
      expect(destroyFns.length).toBe(length - index - 1);
    });

    jest.useRealTimers();
  });

  it('should warning when pass a string as icon props', () => {
    jest.useFakeTimers();
    const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    confirm({
      content: 'some descriptions',
      icon: 'ab',
    });
    jest.runAllTimers();
    expect(warnSpy).not.toHaveBeenCalled();
    confirm({
      content: 'some descriptions',
      icon: 'question',
    });
    jest.runAllTimers();
    expect(warnSpy).toHaveBeenCalledWith(
      `Warning: [antd: Modal] \`icon\` is using ReactNode instead of string naming in v4. Please check \`question\` at https://ant.design/components/icon`,
    );
    warnSpy.mockRestore();
    jest.useRealTimers();
  });

  it('ok button should trigger onOk once when click it many times quickly', () => {
    const onOk = jest.fn();
    open({ onOk });
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    expect(onOk).toHaveBeenCalledTimes(1);
  });

  // https://github.com/ant-design/ant-design/issues/23358
  it('ok button should trigger onOk multiple times when onOk has close argument', () => {
    const onOk = jest.fn();
    open({
      onOk: close => {
        onOk();
        (() => {})(close); // do nothing
      },
    });
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    $$('.ant-btn-primary')[0].click();
    expect(onOk).toHaveBeenCalledTimes(3);
  });

  it('should be able to config rootPrefixCls', () => {
    jest.useFakeTimers();
    Modal.config({
      rootPrefixCls: 'my',
    });
    confirm({
      title: 'title',
    });
    jest.runAllTimers();
    expect(document.querySelectorAll('.ant-btn').length).toBe(0);
    expect(document.querySelectorAll('.my-btn').length).toBe(2);
    expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
    Modal.config({
      rootPrefixCls: 'your',
    });
    confirm({
      title: 'title',
    });
    jest.runAllTimers();
    expect(document.querySelectorAll('.ant-btn').length).toBe(0);
    expect(document.querySelectorAll('.my-btn').length).toBe(2);
    expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
    expect(document.querySelectorAll('.your-btn').length).toBe(2);
    expect(document.querySelectorAll('.your-modal-confirm').length).toBe(1);
    Modal.config({
      rootPrefixCls: 'ant',
    });
    jest.useRealTimers();
  });

  it('trigger afterClose once when click on cancel button', async () => {
    const afterClose = jest.fn();
    open({
      afterClose,
    });
    // first Modal
    $$('.ant-btn')[0].click();
    expect(afterClose).not.toHaveBeenCalled();
    await sleep(500);
    expect(afterClose).toHaveBeenCalled();
  });

  it('trigger afterClose once when click on ok button', async () => {
    const afterClose = jest.fn();
    open({
      afterClose,
    });
    // second Modal
    $$('.ant-btn-primary')[0].click();
    expect(afterClose).not.toHaveBeenCalled();
    await sleep(500);
    expect(afterClose).toHaveBeenCalled();
  });
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name           Close ordered item page on Amazon.co.jp
// @namespace      curipha
// @description    Close ordered item page automatically
// @include        https://www.amazon.co.jp/dp/*
// @include        https://www.amazon.co.jp/*/dp/*
// @exclude        https://www.amazon.co.jp/ap/*
// @exclude        https://www.amazon.co.jp/mn/*
// @exclude        https://www.amazon.co.jp/clouddrive*
// @version        0.0.1
// @grant          none
// @noframes
// ==/UserScript==

(function() {
  'use strict';

  if (document.getElementById('ebooksInstantOrderUpdate')) {
    window.open('about:blank', '_self').close();
  }
})();
<commit_msg>Add a product page URI pattern<commit_after>// ==UserScript==
// @name           Close ordered item page on Amazon.co.jp
// @namespace      curipha
// @description    Close ordered item page automatically
// @include        https://www.amazon.co.jp/dp/*
// @include        https://www.amazon.co.jp/*/dp/*
// @include        https://www.amazon.co.jp/gp/product/*
// @exclude        https://www.amazon.co.jp/ap/*
// @exclude        https://www.amazon.co.jp/mn/*
// @exclude        https://www.amazon.co.jp/clouddrive*
// @version        0.0.1
// @grant          none
// @noframes
// ==/UserScript==

(function() {
  'use strict';

  if (document.getElementById('ebooksInstantOrderUpdate')) {
    window.open('about:blank', '_self').close();
  }
})();
<|endoftext|>