<commit_before>/*
Copyright by Oleg Efimov and node-mysql-libmysqlclient contributors
See contributors list in README

See license text in LICENSE file
*/

// Load configuration
var cfg = require('../config.js');

exports.Connect = function (test) {
  test.expect(2);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err === null, "conn.connect() for allowed database");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.closeSync();

    test.done();
  });
};

exports.ConnectWithError = function (test) {
  test.expect(4);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database_denied, function (err) {
    test.ok(err instanceof Error, "Error object is presented");

    var
      connectErrno = conn.connectErrno,
      connectError = conn.connectError,
      isMatched = connectError.match(new RegExp("Access denied for user '(" + cfg.user + "|)'@'.*' to database '" + cfg.database_denied + "'"));

    test.equals(connectErrno, 1044, "conn.connectErrno");

    if (!isMatched) {
      console.log("Connect error: " + connectError);
    }
    test.ok(isMatched, "conn.connectError");

    test.equals(err.message, "Connection error #" + connectErrno + ": " + connectError, "Callback exception in conn.connect() to denied database");

    test.done();
  });
};

exports.Connect2Times = function (test) {
  test.expect(5);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err === null, "conn.connect() for allowed database");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
      test.ok(err instanceof Error, "conn.connect() fails if already");
      test.equal(err.message, "Already initialized. Use conn.realConnectSync() after conn.initSync()");
      test.ok(arguments.length <= 1, "Only error arguments is possible.");

      conn.closeSync();

      test.done();
    });
  });
};

exports.ConnectAfterConnectSync2 = function (test) {
  test.expect(4);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  test.ok(conn.connectSync(cfg.host, cfg.user, cfg.password, cfg.database));

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err instanceof Error, "conn.connect() fails if already");
    test.equal(err.message, "Already initialized. Use conn.realConnectSync() after conn.initSync()");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.closeSync();

    test.done();
  });
};

exports.Query = function (test) {
  test.expect(2);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.query("SHOW TABLES", function (err, res) {
    test.ok(res, "have result");
    if (!res) {
      test.done();
      return;
    }

    test.ok(res.fieldCount === 1, "show results field count === 1");
    var rows = res.fetchAllSync();
    res.freeSync();
    test.ok(rows.some(function (r) {
      return r['Tables_in_' + cfg.database] === cfg.test_table;
    }), "find the test table in results");
    conn.closeSync();
    test.done();
  });
};

exports.QueryWithLastInsertIdAndAffectedRows = function (test) {
  test.expect(8);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  res = conn.querySync("ALTER TABLE " + cfg.test_table + " AUTO_INCREMENT = 1;");
  test.strictEqual(res, true);

  conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');", function (err, info) {
    test.equals(info.insertId, 1, "Last insert id");

    conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('2', '0'), ('3', '0');", function (err, info) {
      // For MySQL version >= 5.1.12
      test.equals(info.insertId, 2, "Last insert id");

      conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('4', '1'), ('5', '1');", function (err, info) {
        // For MySQL version >= 5.1.12
        test.equals(info.insertId, 4, "Last insert id");

        var insertIdSync = conn.lastInsertIdSync();
        test.equals(insertIdSync, 4, "Last insert id");

        conn.query("UPDATE " + cfg.test_table + " SET random_number = '0' WHERE random_boolean='0';", function (err, info) {
          test.equals(info.affectedRows, 3, "Affected rows count");

          conn.query("DELETE FROM " + cfg.test_table + " WHERE random_boolean='1';", function (err, info) {
            test.equals(info.affectedRows, 2, "Affected rows count");

            conn.closeSync();
            test.done();
          });
        });
      });
    });
  });
};

exports.QueryWithError = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.query("SHOW TABLESaagh", function (err, res) {
    test.ok(err, "Error object is present");
    test.ok(!res, "Result is not defined");

    var
      errno = conn.errnoSync(),
      error = conn.errorSync();

    test.equals(errno, 1064, "conn.connectErrno");
    test.ok(error.match(new RegExp("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'TABLESaagh'")), "conn.connectError");

    test.equals(err.message, "Query error #" + errno + ": " + error, "Callback exception in conn.query()");

    conn.closeSync();
    test.done();
  });
};

exports.QueryWithoutCallback = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  test.doesNotThrow(function () {
    conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');");
  });

  conn.query("SELECT random_number, random_boolean FROM " + cfg.test_table + ";", function (err, res) {
    test.ok(err === null, "Error object is not present");
    test.ok(res instanceof cfg.mysql_bindings.MysqlResult, "res instanceof MysqlResult");

    var rows = res.fetchAllSync();
    test.same(rows, [{random_number: 1, random_boolean: 0}],
              "Right result, one row, [{random_number: 1, random_boolean: 0}]]");
    conn.closeSync();
    test.done();
  });
};

exports.QuerySend = function (test) {
  test.expect(2);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.querySend("SHOW TABLES", function (err, res) {
    test.ok(res.fieldCount === 1, "show results field count === 1");
    var rows = res.fetchAllSync();
    res.freeSync();
    test.ok(rows.some(function (r) {
      return r['Tables_in_' + cfg.database] === cfg.test_table;
    }), "find the test table in results");
    conn.closeSync();
    test.done();
  });
};

exports.QuerySendWithLastInsertIdAndAffectedRows = function (test) {
  test.expect(8);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  res = conn.querySync("ALTER TABLE " + cfg.test_table + " AUTO_INCREMENT = 1;");
  test.strictEqual(res, true);

  conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');", function (err, info) {
    test.equals(info.insertId, 1, "Last insert id");

    conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('2', '0'), ('3', '0');", function (err, info) {
      // For MySQL version >= 5.1.12
      test.equals(info.insertId, 2, "Last insert id");

      conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('4', '1'), ('5', '1');", function (err, info) {
        // For MySQL version >= 5.1.12
        test.equals(info.insertId, 4, "Last insert id");

        var insertIdSync = conn.lastInsertIdSync();
        test.equals(insertIdSync, 4, "Last insert id");

        conn.querySend("UPDATE " + cfg.test_table + " SET random_number = '0' WHERE random_boolean='0';", function (err, info) {
          test.equals(info.affectedRows, 3, "Affected rows count");

          conn.querySend("DELETE FROM " + cfg.test_table + " WHERE random_boolean='1';", function (err, info) {
            test.equals(info.affectedRows, 2, "Affected rows count");

            conn.closeSync();
            test.done();
          });
        });
      });
    });
  });
};

exports.QuerySendWithError = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.querySend("SHOW TABLESaagh", function (err, res) {
    test.ok(err, "Error object is present");
    test.ok(!res, "Result is not defined");

    var
      errno = conn.errnoSync(),
      error = conn.errorSync();

    test.equals(errno, 1064, "conn.connectErrno");
    test.ok(error.match(new RegExp("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'TABLESaagh'")), "conn.connectError");

    test.equals(err.message, "Query error #" + errno + ": " + error, "Callback exception in conn.query()");

    conn.closeSync();
    test.done();
  });
};
<commit_msg>Fix async Query test<commit_after>/*
Copyright by Oleg Efimov and node-mysql-libmysqlclient contributors
See contributors list in README

See license text in LICENSE file
*/

// Load configuration
var cfg = require('../config.js');

exports.Connect = function (test) {
  test.expect(2);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err === null, "conn.connect() for allowed database");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.closeSync();

    test.done();
  });
};

exports.ConnectWithError = function (test) {
  test.expect(4);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database_denied, function (err) {
    test.ok(err instanceof Error, "Error object is presented");

    var
      connectErrno = conn.connectErrno,
      connectError = conn.connectError,
      isMatched = connectError.match(new RegExp("Access denied for user '(" + cfg.user + "|)'@'.*' to database '" + cfg.database_denied + "'"));

    test.equals(connectErrno, 1044, "conn.connectErrno");

    if (!isMatched) {
      console.log("Connect error: " + connectError);
    }
    test.ok(isMatched, "conn.connectError");

    test.equals(err.message, "Connection error #" + connectErrno + ": " + connectError, "Callback exception in conn.connect() to denied database");

    test.done();
  });
};

exports.Connect2Times = function (test) {
  test.expect(5);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err === null, "conn.connect() for allowed database");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
      test.ok(err instanceof Error, "conn.connect() fails if already");
      test.equal(err.message, "Already initialized. Use conn.realConnectSync() after conn.initSync()");
      test.ok(arguments.length <= 1, "Only error arguments is possible.");

      conn.closeSync();

      test.done();
    });
  });
};

exports.ConnectAfterConnectSync2 = function (test) {
  test.expect(4);

  var conn = new cfg.mysql_bindings.MysqlConnection();

  test.ok(conn.connectSync(cfg.host, cfg.user, cfg.password, cfg.database));

  conn.connect(cfg.host, cfg.user, cfg.password, cfg.database, function (err) {
    test.ok(err instanceof Error, "conn.connect() fails if already");
    test.equal(err.message, "Already initialized. Use conn.realConnectSync() after conn.initSync()");
    test.ok(arguments.length <= 1, "Only error arguments is possible.");

    conn.closeSync();

    test.done();
  });
};

exports.Query = function (test) {
  test.expect(3);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.query("SHOW TABLES", function (err, res) {
    test.ok(res, "have result");
    if (!res) {
      test.done();
      return;
    }

    test.ok(res.fieldCount === 1, "show results field count === 1");
    var rows = res.fetchAllSync();
    res.freeSync();
    test.ok(rows.some(function (r) {
      return r['Tables_in_' + cfg.database] === cfg.test_table;
    }), "find the test table in results");
    conn.closeSync();
    test.done();
  });
};

exports.QueryWithLastInsertIdAndAffectedRows = function (test) {
  test.expect(8);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  res = conn.querySync("ALTER TABLE " + cfg.test_table + " AUTO_INCREMENT = 1;");
  test.strictEqual(res, true);

  conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');", function (err, info) {
    test.equals(info.insertId, 1, "Last insert id");

    conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('2', '0'), ('3', '0');", function (err, info) {
      // For MySQL version >= 5.1.12
      test.equals(info.insertId, 2, "Last insert id");

      conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('4', '1'), ('5', '1');", function (err, info) {
        // For MySQL version >= 5.1.12
        test.equals(info.insertId, 4, "Last insert id");

        var insertIdSync = conn.lastInsertIdSync();
        test.equals(insertIdSync, 4, "Last insert id");

        conn.query("UPDATE " + cfg.test_table + " SET random_number = '0' WHERE random_boolean='0';", function (err, info) {
          test.equals(info.affectedRows, 3, "Affected rows count");

          conn.query("DELETE FROM " + cfg.test_table + " WHERE random_boolean='1';", function (err, info) {
            test.equals(info.affectedRows, 2, "Affected rows count");

            conn.closeSync();
            test.done();
          });
        });
      });
    });
  });
};

exports.QueryWithError = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.query("SHOW TABLESaagh", function (err, res) {
    test.ok(err, "Error object is present");
    test.ok(!res, "Result is not defined");

    var
      errno = conn.errnoSync(),
      error = conn.errorSync();

    test.equals(errno, 1064, "conn.connectErrno");
    test.ok(error.match(new RegExp("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'TABLESaagh'")), "conn.connectError");

    test.equals(err.message, "Query error #" + errno + ": " + error, "Callback exception in conn.query()");

    conn.closeSync();
    test.done();
  });
};

exports.QueryWithoutCallback = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  test.doesNotThrow(function () {
    conn.query("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');");
  });

  conn.query("SELECT random_number, random_boolean FROM " + cfg.test_table + ";", function (err, res) {
    test.ok(err === null, "Error object is not present");
    test.ok(res instanceof cfg.mysql_bindings.MysqlResult, "res instanceof MysqlResult");

    var rows = res.fetchAllSync();
    test.same(rows, [{random_number: 1, random_boolean: 0}],
              "Right result, one row, [{random_number: 1, random_boolean: 0}]]");
    conn.closeSync();
    test.done();
  });
};

exports.QuerySend = function (test) {
  test.expect(2);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.querySend("SHOW TABLES", function (err, res) {
    test.ok(res.fieldCount === 1, "show results field count === 1");
    var rows = res.fetchAllSync();
    res.freeSync();
    test.ok(rows.some(function (r) {
      return r['Tables_in_' + cfg.database] === cfg.test_table;
    }), "find the test table in results");
    conn.closeSync();
    test.done();
  });
};

exports.QuerySendWithLastInsertIdAndAffectedRows = function (test) {
  test.expect(8);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
    res;

  res = conn.querySync("DELETE FROM " + cfg.test_table + ";");
  test.strictEqual(res, true);

  res = conn.querySync("ALTER TABLE " + cfg.test_table + " AUTO_INCREMENT = 1;");
  test.strictEqual(res, true);

  conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('1', '0');", function (err, info) {
    test.equals(info.insertId, 1, "Last insert id");

    conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('2', '0'), ('3', '0');", function (err, info) {
      // For MySQL version >= 5.1.12
      test.equals(info.insertId, 2, "Last insert id");

      conn.querySend("INSERT INTO " + cfg.test_table + " (random_number, random_boolean) VALUES ('4', '1'), ('5', '1');", function (err, info) {
        // For MySQL version >= 5.1.12
        test.equals(info.insertId, 4, "Last insert id");

        var insertIdSync = conn.lastInsertIdSync();
        test.equals(insertIdSync, 4, "Last insert id");

        conn.querySend("UPDATE " + cfg.test_table + " SET random_number = '0' WHERE random_boolean='0';", function (err, info) {
          test.equals(info.affectedRows, 3, "Affected rows count");

          conn.querySend("DELETE FROM " + cfg.test_table + " WHERE random_boolean='1';", function (err, info) {
            test.equals(info.affectedRows, 2, "Affected rows count");

            conn.closeSync();
            test.done();
          });
        });
      });
    });
  });
};

exports.QuerySendWithError = function (test) {
  test.expect(5);

  var
    conn = cfg.mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database);

  conn.querySend("SHOW TABLESaagh", function (err, res) {
    test.ok(err, "Error object is present");
    test.ok(!res, "Result is not defined");

    var
      errno = conn.errnoSync(),
      error = conn.errorSync();

    test.equals(errno, 1064, "conn.connectErrno");
    test.ok(error.match(new RegExp("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'TABLESaagh'")), "conn.connectError");

    test.equals(err.message, "Query error #" + errno + ": " + error, "Callback exception in conn.query()");

    conn.closeSync();
    test.done();
  });
};
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { actividadMock } from '../../helpers/mocks'
import { xmlBloqueEmpezarAEjecutar } from 'pilasbloques/models/desafio'
import sinon from 'sinon'
import { setUpTestLocale } from '../../helpers/utils'

let ctrl
let version
let actividad = actividadMock.nombre
let solucion = "PHhtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCI+PHZhcmlhYmxlcz48L3ZhcmlhYmxlcz48YmxvY2sgdHlwZT0iYWxfZW1wZXphcl9hX2VqZWN1dGFyIiBpZD0idX4vczBQV1BEWkQ1aFEtLFFnPXQiIGRlbGV0YWJsZT0iZmFsc2UiIG1vdmFibGU9ImZhbHNlIiBlZGl0YWJsZT0iZmFsc2UiIHg9IjIyNyIgeT0iMTUiPjwvYmxvY2s+PC94bWw+"

module('Unit | Components | challenge-workspace-buttons', function (hooks) {
  setupTest(hooks);
  setUpTestLocale(hooks)

  hooks.beforeEach(function () {
    ctrl = this.owner.factoryFor('component:challenge-workspace-buttons').create()
    ctrl.set('actividad', actividadMock)
    ctrl.descargar = sinon.stub()
    version = ctrl.version()
    sinon.resetHistory()
  });

  test("Al guardar solución crea el archivo correctamente", function (assert) {
    let contenido = JSON.stringify({
      version,
      actividad,
      solucion: "bnVsbA=="
    })
    let archivo = `${actividad}.spbq`
    let tipo = 'application/octet-stream'

    ctrl.send("guardarSolucion")
    assert.ok(ctrl.descargar.calledWith(contenido, archivo, tipo))
  })

  let solucionCompleta = {
    version,
    actividad,
    solucion
  }

  test("Al borrar una solucion con un workspace inicial se deberia volver al workspace incial", function (assert) {
    const actividadConWorkspaceInicial = this.owner.lookup('service:store').createRecord('desafio', { solucionInicial: solucion })
    ctrl.set('actividad', actividadConWorkspaceInicial)
    ctrl.set('workspace', 'algoDistinto')
    ctrl.send('borrarSolucion')
    assert.equal(ctrl.get('workspace'), solucion)
  })

  test("Al borrar una solucion sin un workspace inicial se deberia volver al wokspace inicial base", function (assert) {
    const actividadSinWorkspaceInicial = this.owner.lookup('service:store').createRecord('desafio')
    ctrl.set('actividad', actividadSinWorkspaceInicial)
    ctrl.set('workspace', 'algoDistinto')
    ctrl.send('borrarSolucion')
    assert.equal(ctrl.get('workspace'), xmlBloqueEmpezarAEjecutar)
  })

  goodFileTest("Carga un archivo de solución correctamente", solucionCompleta)

  let solucionCompletaConVersionPosterior = {
    version: 999,
    actividad,
    solucion
  }

  goodFileTest("Carga un archivo de solución aunque tenga una versión posterior", solucionCompletaConVersionPosterior)

  let solucionCompletaConVersionAnterior = {
    version: -1,
    actividad,
    solucion
  }

  failFileTest("Verifica que se está cargando una versión anterior", solucionCompletaConVersionAnterior, function (assert, err) {
    assert.equal(err, "¡Cuidado! El archivo indica que es de una versión anterior. Se cargará de todas formas, pero te sugerimos que resuelvas nuevamente el ejercicio y guardes un nuevo archivo.")
  })

  failFileTest("Aunque no tenga una versión actual se carga al workspace", solucionCompletaConVersionAnterior, function (assert) {
    assert.ok(ctrl.get("workspace"))
  })

  let solucionCompletaSinVersion = {
    actividad,
    solucion
  }

  goodFileTest("Carga un archivo de solución aunque no tenga versión", solucionCompletaSinVersion)

  let solucionParaOtraActividad = {
    version,
    actividad: "Otra_Actividad",
    solucion
  }

  failFileTest("Verifica que sea para la actividad que se está cargando", solucionParaOtraActividad, function (assert, err) {
    assert.equal(err, "¡Cuidado! El archivo indica que es para otra actividad (Otra_Actividad). Se cargará de todas formas, pero puede fallar.")
  })

  failFileTest("Aunque no sea una solución para la actividad se carga al workspace", solucionParaOtraActividad, function (assert) {
    assert.ok(ctrl.get("workspace"))
  })

  let solucionCompletaConVersionAnteriorParaOtraActividad = {
    version: -1,
    actividad: "Otra_Actividad",
    solucion
  }

  failFileTest("Acumula las validaciones con soluciones", solucionCompletaConVersionAnteriorParaOtraActividad, function (assert, err) {
    assert.equal(err,
      `¡Cuidado! El archivo indica que es para otra actividad (Otra_Actividad). Se cargará de todas formas, pero puede fallar.
¡Cuidado! El archivo indica que es de una versión anterior. Se cargará de todas formas, pero te sugerimos que resuelvas nuevamente el ejercicio y guardes un nuevo archivo.`)
  })

  failFileTest("Aunque no tenga versión actual y sea una solución para la actividad se carga al workspace", solucionCompletaConVersionAnteriorParaOtraActividad, function (assert) {
    assert.ok(ctrl.get("workspace"))
  })

  let archivoSinSolucion = {
    version,
    actividad,
  }

  failFileTest("Verifica que tenga una solucion", archivoSinSolucion, function (assert, err) {
    assert.equal(err, "Este archivo no tiene una solución de Pilas Bloques.")
  })

  failFileTest("Verifica que tenga una solucion", archivoSinSolucion, function (assert) {
    assert.notOk(ctrl.get("workspace"))
  })

  function goodFileTest(mensaje, contenido) {
    fileTest(mensaje, contenido, (assert) => { assert.ok(ctrl.get("workspace")) }, () => { })
  }

  function failFileTest(mensaje, contenido, cb) {
    fileTest(mensaje, contenido, () => { }, cb)
  }

  function fileTest(mensaje, contenido, cbGood, cbFail) {
    test(mensaje, function (assert) {
      let done = assert.async()
      let archivo = new Blob([JSON.stringify(contenido)])

      ctrl
        .leerSolucionWeb(archivo)
        .then(() => {
          cbGood(assert)
          done()
        })
        .catch((err) => {
          cbFail(assert, err)
          done()
        })
    })
  }
});

<commit_msg>fix challenge-workspace-buttons tests<commit_after>import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { actividadMock } from '../../helpers/mocks'
import { xmlBloqueEmpezarAEjecutar } from 'pilasbloques/models/desafio'
import sinon from 'sinon'
import { setUpTestLocale } from '../../helpers/utils'

module('Unit | Components | challenge-workspace-buttons', function (hooks) {
  setupTest(hooks);
  setUpTestLocale(hooks)

  let ctrl
  let version
  let actividad = actividadMock.nombre
  let solucion = "PHhtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCI+PHZhcmlhYmxlcz48L3ZhcmlhYmxlcz48YmxvY2sgdHlwZT0iYWxfZW1wZXphcl9hX2VqZWN1dGFyIiBpZD0idX4vczBQV1BEWkQ1aFEtLFFnPXQiIGRlbGV0YWJsZT0iZmFsc2UiIG1vdmFibGU9ImZhbHNlIiBlZGl0YWJsZT0iZmFsc2UiIHg9IjIyNyIgeT0iMTUiPjwvYmxvY2s+PC94bWw+"
  let currentWorkspace

  hooks.beforeEach(function () {
    ctrl = this.owner.factoryFor('component:challenge-workspace-buttons').create()
    ctrl.set('actividad', actividadMock)
    ctrl.descargar = sinon.stub()
    currentWorkspace = ""
    ctrl.setWorkspace = function (xml) {
      currentWorkspace = xml
    }
    version = ctrl.version()
    sinon.resetHistory()
  });

  test("Al guardar solución crea el archivo correctamente", function (assert) {
    let contenido = JSON.stringify({
      version,
      actividad,
      solucion: "bnVsbA=="
    })
    let archivo = `${actividad}.spbq`
    let tipo = 'application/octet-stream'

    ctrl.send("guardarSolucion")
    assert.ok(ctrl.descargar.calledWith(contenido, archivo, tipo))
  })

  let solucionCompleta = {
    version,
    actividad,
    solucion
  }

  test("Al borrar una solucion con un workspace inicial se deberia volver al workspace incial", function (assert) {
    const actividadConWorkspaceInicial = this.owner.lookup('service:store').createRecord('desafio', { solucionInicial: solucion })
    ctrl.set('actividad', actividadConWorkspaceInicial)
    currentWorkspace = "algoDistinto"
    ctrl.send('borrarSolucion')
    assert.equal(currentWorkspace, solucion)
  })

  test("Al borrar una solucion sin un workspace inicial se deberia volver al wokspace inicial base", function (assert) {
    const actividadSinWorkspaceInicial = this.owner.lookup('service:store').createRecord('desafio')
    ctrl.set('actividad', actividadSinWorkspaceInicial)
    currentWorkspace = "algoDistinto"
    ctrl.send('borrarSolucion')
    assert.equal(currentWorkspace, xmlBloqueEmpezarAEjecutar)
  })

  goodFileTest("Carga un archivo de solución correctamente", solucionCompleta)

  let solucionCompletaConVersionPosterior = {
    version: 999,
    actividad,
    solucion
  }

  goodFileTest("Carga un archivo de solución aunque tenga una versión posterior", solucionCompletaConVersionPosterior)

  let solucionCompletaConVersionAnterior = {
    version: -1,
    actividad,
    solucion
  }

  failFileTest("Verifica que se está cargando una versión anterior", solucionCompletaConVersionAnterior, function (assert, err) {
    assert.equal(err, "¡Cuidado! El archivo indica que es de una versión anterior. Se cargará de todas formas, pero te sugerimos que resuelvas nuevamente el ejercicio y guardes un nuevo archivo.")
  })

  failFileTest("Aunque no tenga una versión actual se carga al workspace", solucionCompletaConVersionAnterior, function (assert) {
    assert.ok(currentWorkspace)
  })

  let solucionCompletaSinVersion = {
    actividad,
    solucion
  }

  goodFileTest("Carga un archivo de solución aunque no tenga versión", solucionCompletaSinVersion)

  let solucionParaOtraActividad = {
    version,
    actividad: "Otra_Actividad",
    solucion
  }

  failFileTest("Verifica que sea para la actividad que se está cargando", solucionParaOtraActividad, function (assert, err) {
    assert.equal(err, "¡Cuidado! El archivo indica que es para otra actividad (Otra_Actividad). Se cargará de todas formas, pero puede fallar.")
  })

  failFileTest("Aunque no sea una solución para la actividad se carga al workspace", solucionParaOtraActividad, function (assert) {
    assert.ok(currentWorkspace)
  })

  let solucionCompletaConVersionAnteriorParaOtraActividad = {
    version: -1,
    actividad: "Otra_Actividad",
    solucion
  }

  failFileTest("Acumula las validaciones con soluciones", solucionCompletaConVersionAnteriorParaOtraActividad, function (assert, err) {
    assert.equal(err,
      `¡Cuidado! El archivo indica que es para otra actividad (Otra_Actividad). Se cargará de todas formas, pero puede fallar.
¡Cuidado! El archivo indica que es de una versión anterior. Se cargará de todas formas, pero te sugerimos que resuelvas nuevamente el ejercicio y guardes un nuevo archivo.`)
  })

  failFileTest("Aunque no tenga versión actual y sea una solución para la actividad se carga al workspace", solucionCompletaConVersionAnteriorParaOtraActividad, function (assert) {
    assert.ok(currentWorkspace)
  })

  let archivoSinSolucion = {
    version,
    actividad,
  }

  failFileTest("Verifica que tenga una solucion", archivoSinSolucion, function (assert, err) {
    assert.equal(err, "Este archivo no tiene una solución de Pilas Bloques.")
  })

  failFileTest("Verifica que tenga una solucion", archivoSinSolucion, function (assert) {
    assert.notOk(currentWorkspace)
  })

  function goodFileTest(mensaje, contenido) {
    fileTest(mensaje, contenido, (assert) => { assert.ok(currentWorkspace) }, () => { })
  }

  function failFileTest(mensaje, contenido, cb) {
    fileTest(mensaje, contenido, () => { }, cb)
  }

  function fileTest(mensaje, contenido, cbGood, cbFail) {
    test(mensaje, function (assert) {
      let done = assert.async()
      let archivo = new Blob([JSON.stringify(contenido)])

      ctrl
        .leerSolucionWeb(archivo)
        .then(() => {
          cbGood(assert)
          done()
        })
        .catch((err) => {
          cbFail(assert, err)
          done()
        })
    })
  }
});

<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';

import TextField from '../components/TextField';
import NumberField from '../components/NumberField';
import Button from '../components/Button';

import { Button as UIButton, List, Form, Segment } from 'semantic-ui-react';

export default class RoutineFormIntervals extends Component {
  render() {
    const { actions, intervals, groupIndex, groupId } = this.props;

    const intervalFields = intervals.map((interval, index) => {
      return (
        <Form.Group key={index} inline style={{ alignItems: "flex-end" }}>
          <TextField
            label="Name"
            name={`routine.group[${groupIndex}].interval[${index}].name`}
            value={interval.name}
            onChange={(ev) =>
              actions.changeInterval(
                groupId, interval.id, "name", ev.target.value)} />
          <NumberField
            label="Duration"
            name={`routine.group[${groupIndex}].interval[${index}].duration`}
            value={interval.duration}
            onChange={(ev) =>
              actions.changeInterval(
                groupId,
                interval.id,
                "duration",
                parseInt(ev.target.value, 10)
              )} />
          <Button
            color="blue"
            icon="chevron up"
            onClick={actions.moveIntervalUp}
            groupId={groupId}
            intervalId={interval.id}
            order={interval.order} />
          <Button
            color="blue"
            icon="chevron down"
            onClick={actions.moveIntervalDown}
            groupId={groupId}
            intervalId={interval.id} />
          <Button
            color="red"
            icon="remove"
            onClick={actions.removeInterval}
            groupId={groupId}
            intervalId={interval.id} />
        </Form.Group>
      );
    });

    return (
      <List.Item>
        <List.Header as="h4">Intervals</List.Header>
        {intervalFields}
        <Button
          float="right"
          size="small"
          color="green"
          label="Add New Interval"
          icon="plus"
          onClick={actions.addInterval}
          groupId={groupId} />
      </List.Item>
    );
  }
}

RoutineFormIntervals.propTypes = {
  actions: PropTypes.object.isRequired,
  intervals: PropTypes.array.isRequired,
  groupIndex: PropTypes.number.isRequired,
  groupId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}
<commit_msg>Remove unused imports<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';

import TextField from '../components/TextField';
import NumberField from '../components/NumberField';
import Button from '../components/Button';

import { List, Form } from 'semantic-ui-react';

export default class RoutineFormIntervals extends Component {
  render() {
    const { actions, intervals, groupIndex, groupId } = this.props;

    const intervalFields = intervals.map((interval, index) => {
      return (
        <Form.Group key={index} inline style={{ alignItems: "flex-end" }}>
          <TextField
            label="Name"
            name={`routine.group[${groupIndex}].interval[${index}].name`}
            value={interval.name}
            onChange={(ev) =>
              actions.changeInterval(
                groupId, interval.id, "name", ev.target.value)} />
          <NumberField
            label="Duration"
            name={`routine.group[${groupIndex}].interval[${index}].duration`}
            value={interval.duration}
            onChange={(ev) =>
              actions.changeInterval(
                groupId,
                interval.id,
                "duration",
                parseInt(ev.target.value, 10)
              )} />
          <Button
            color="blue"
            icon="chevron up"
            onClick={actions.moveIntervalUp}
            groupId={groupId}
            intervalId={interval.id}
            order={interval.order} />
          <Button
            color="blue"
            icon="chevron down"
            onClick={actions.moveIntervalDown}
            groupId={groupId}
            intervalId={interval.id} />
          <Button
            color="red"
            icon="remove"
            onClick={actions.removeInterval}
            groupId={groupId}
            intervalId={interval.id} />
        </Form.Group>
      );
    });

    return (
      <List.Item>
        <List.Header as="h4">Intervals</List.Header>
        {intervalFields}
        <Button
          float="right"
          size="small"
          color="green"
          label="Add New Interval"
          icon="plus"
          onClick={actions.addInterval}
          groupId={groupId} />
      </List.Item>
    );
  }
}

RoutineFormIntervals.propTypes = {
  actions: PropTypes.object.isRequired,
  intervals: PropTypes.array.isRequired,
  groupIndex: PropTypes.number.isRequired,
  groupId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { GUI } from '../../constants/gui_types'
import { BrowseIcon, CameraIcon, CancelIcon, PostIcon, ReplyAllIcon } from './EditorIcons'

class PostActionBar extends Component {

  static propTypes = {
    cancelAction: PropTypes.func.isRequired,
    dispatch: PropTypes.func.isRequired,
    disableSubmitAction: PropTypes.bool,
    editorId: PropTypes.string.isRequired,
    handleFileAction: PropTypes.func.isRequired,
    replyAllAction: PropTypes.func,
    submitAction: PropTypes.func.isRequired,
    submitText: PropTypes.string,
  }

  submitted = () => {
    const { submitAction } = this.props
    submitAction()
  }

  handleFileBrowser = (e) => {
    const { handleFileAction } = this.props
    handleFileAction(e)
  }

  browse = () => {
    this.refs.browseButton.blur()
    this.refs.FileBrowser.click()
  }

  cancel = () => {
    this.props.cancelAction()
  }

  render() {
    const { disableSubmitAction, replyAllAction, submitText } = this.props
    return (

        <button className="PostActionButton forUpload" ref="browseButton" onClick={ this.browse }>
          Upload
          { GUI.viewportDeviceSize === 'mobile' ? <CameraIcon /> : <BrowseIcon /> }
        </button>

        <button className="PostActionButton forCancel" ref="cancelButton" onClick={ this.cancel }>
          Cancel
          <CancelIcon />
        </button>

        {
          replyAllAction ?
            <button className="PostActionButton forReplyAll" onClick={ replyAllAction }>
              Reply All
              <ReplyAllIcon />
            </button> :
            null
        }

        <button
          className={ `PostActionButton for${submitText}` }
          disabled={ disableSubmitAction }
          ref="submitButton"
          onClick={ this.submitted }
        >
          { submitText }
          <PostIcon />
        </button>

        <input
          className="hidden"
          onChange={ this.handleFileBrowser }
          ref="FileBrowser"
          type="file"
          accept="image/*"
          multiple
        />

    )
  }
}

export default connect()(PostActionBar)

<commit_msg>fixes the bug where you can't upload the same image twice<commit_after>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { GUI } from '../../constants/gui_types'
import { BrowseIcon, CameraIcon, CancelIcon, PostIcon, ReplyAllIcon } from './EditorIcons'

class PostActionBar extends Component {

  static propTypes = {
    cancelAction: PropTypes.func.isRequired,
    dispatch: PropTypes.func.isRequired,
    disableSubmitAction: PropTypes.bool,
    editorId: PropTypes.string.isRequired,
    handleFileAction: PropTypes.func.isRequired,
    replyAllAction: PropTypes.func,
    submitAction: PropTypes.func.isRequired,
    submitText: PropTypes.string,
  }

  submitted = () => {
    const { submitAction } = this.props
    submitAction()
  }

  handleFileBrowser = (e) => {
    const { handleFileAction } = this.props
    handleFileAction(e)
    this.refs.FileBrowser.value = ''
  }

  browse = () => {
    this.refs.browseButton.blur()
    this.refs.FileBrowser.click()
  }

  cancel = () => {
    this.props.cancelAction()
  }

  render() {
    const { disableSubmitAction, replyAllAction, submitText } = this.props
    return (

        <button className="PostActionButton forUpload" ref="browseButton" onClick={ this.browse }>
          Upload
          { GUI.viewportDeviceSize === 'mobile' ? <CameraIcon /> : <BrowseIcon /> }
        </button>

        <button className="PostActionButton forCancel" ref="cancelButton" onClick={ this.cancel }>
          Cancel
          <CancelIcon />
        </button>

        {
          replyAllAction ?
            <button className="PostActionButton forReplyAll" onClick={ replyAllAction }>
              Reply All
              <ReplyAllIcon />
            </button> :
            null
        }

        <button
          className={ `PostActionButton for${submitText}` }
          disabled={ disableSubmitAction }
          ref="submitButton"
          onClick={ this.submitted }
        >
          { submitText }
          <PostIcon />
        </button>

        <input
          className="hidden"
          onChange={ this.handleFileBrowser }
          ref="FileBrowser"
          type="file"
          accept="image/*"
          multiple
        />

    )
  }
}

export default connect()(PostActionBar)

<|endoftext|>


<commit_before>import React, {Component} from "react";
import gql from "graphql-tag.macro";
import {graphql, withApollo} from "react-apollo";
import {Container, Row, Col, Card, CardBody, Button} from "helpers/reactstrap";
import Tour from "helpers/tourHelper";
import Decompress from "./Decompress";
import Door from "./Door";
import {Clamps} from "../Docking/graphics";
import "./style.scss";
import SubscriptionHelper from "helpers/subscriptionHelper";
import DamageOverlay from "../helpers/DamageOverlay";
import {FaCheck, FaArrowRight} from "react-icons/fa";

export const SHUTTLE_SUB = gql`
  subscription ShuttlesUpdate($simulatorId: ID) {
    dockingUpdate(simulatorId: $simulatorId, type: shuttlebay) {
      id
      name
      clamps
      compress
      doors
      image
      docked
      damage {
        damaged
      }
      direction
    }
  }
`;

const trainingSteps = [
  {
    selector: ".shuttles-card",
    content:
      "Shuttles are small ships which can be stored inside of your ship. Most shuttles are piloted and can transport large amounts of supplies or personnel great distances.",
  },
  {
    selector: ".departure-button",
    content:
      'To use a shuttle, first click "Prepare for departure" to begin the departure sequence.',
  },
  {
    selector: ".clamps-button",
    content:
      "You must then release the docking clamps which hold the shuttle in place. This button shows whether the clamps are attached or detached.",
  },
  {
    selector: ".compress-button",
    content:
      "Then, you must decompress the shuttlebay. To board the shuttle, there must be air inside the shuttlebay. However, if the shuttlebay doors are opened without decompressing the shuttlebay, all of that air would be sucked into space. The change in pressure would also pull people and equipment into space as well, so decompressing the shuttlebay is an important safety step.",
  },
  {
    selector: ".doors-button",
    content:
      "Finally, you can open the shuttlebay doors. Once the image of the shuttle disappears, you can know that the shuttle has disembarked from your ship.",
  },
  {
    selector: ".nothing",
    content: "To dock a shuttle, follow the steps in the opposite order.",
  },
];
class Shuttles extends Component {
  render() {
    if (this.props.data.loading || !this.props.data.docking) return null;
    const {docking} = this.props.data;
    if (!docking) return null;
    return (
      <Container fluid className="shuttles-card">
        <SubscriptionHelper
          subscribe={() =>
            this.props.data.subscribeToMore({
              document: SHUTTLE_SUB,
              variables: {
                simulatorId: this.props.simulator.id,
              },
              updateQuery: (previousResult, {subscriptionData}) => {
                return Object.assign({}, previousResult, {
                  docking: subscriptionData.data.dockingUpdate,
                });
              },
            })
          }
        />
        {
          <Row>
            {docking.map((d, i) => (

                <ShuttleBay
                  {...d}
                  i={i}
                  client={this.props.client}
                  simulatorId={this.props.simulator.id}
                />

            ))}
          </Row>
        }
        <Tour steps={trainingSteps} client={this.props.clientObj} />
      </Container>
    );
  }
}

class ShuttleBay extends Component {
  state = {animating: null};
  toggleShuttle = (id, which) => {
    this.setState({animating: which});
    const mutation = gql`
      mutation UpdateShuttleBay($port: DockingPortInput!) {
        updateDockingPort(port: $port)
      }
    `;
    const port = {
      id,
    };
    port[which] = !this.props[which];
    this.props.client.mutate({
      mutation,
      variables: {port},
    });
    if (this.clearTimeoutId) clearTimeout(this.clearTimeoutId);
    this.clearTimeoutId = setTimeout(
      () => this.setState({animating: null}),
      4000,
    );
  };
  updateShuttle = (id, which, value) => {
    const mutation = gql`
      mutation UpdateShuttleBay($port: DockingPortInput!) {
        updateDockingPort(port: $port)
      }
    `;
    const port = {
      id,
    };
    port[which] = value;
    this.props.client.mutate({
      mutation,
      variables: {port},
    });
  };
  render() {
    const {
      docked,
      image,
      name,
      i,
      id,
      clamps,
      compress,
      doors,
      damage,
      direction,
    } = this.props;
    const {animating} = this.state;

    let hint = {clamps: "", compress: "", doors: ""};

    let shuttleStatusMsg = "";
    if (direction === "departing" && docked) {
      shuttleStatusMsg = "Preparing shuttle for departure.";

      if (!clamps) hint.clamps = "ok";
      if (!compress) hint.compress = "ok";
      if (!doors) hint.doors = "ok";

      if (clamps) hint.clamps = "attention";
      else if (compress) hint.compress = "attention";
      else if (doors) hint.doors = "attention";
      else shuttleStatusMsg = "Shuttle is departing.";
    } else if (direction === "departing" && !docked) {
      shuttleStatusMsg = "Shuttle has departed. Secure shuttle bay.";

      if (!clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";

      if (!doors) hint.doors = "attention";
      else if (!compress) hint.compress = "attention";
      else shuttleStatusMsg = "Shuttle bay secured.";
    } else if (direction === "arriving" && !docked) {
      shuttleStatusMsg = "Shuttle approaching. Prepare for arrival.";

      if (!clamps) hint.clamps = "ok";
      if (!compress) hint.compress = "ok";
      if (!doors) hint.doors = "ok";

      if (clamps) hint.clamps = "attention";
      else if (compress) hint.compress = "attention";
      else if (doors) hint.doors = "attention";
      else shuttleStatusMsg = "The shuttle is entering the bay. Please wait.";
    } else if (direction === "arriving" && docked) {
      shuttleStatusMsg = "Shuttle has arrived. Please secure shuttle.";

      if (clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";

      if (!doors) hint.doors = "attention";
      else if (!compress) hint.compress = "attention";
      else if (!clamps) hint.clamps = "attention";
      else shuttleStatusMsg = "Shuttle secured.";
    } else if (!docked) {
      if (!clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";
    } else if (docked) {
      if (clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";
    }

    var hintStrings = {
      ok: <FaCheck />,
      attention: <FaArrowRight />,
    };

    return (
      <Card>
        <DamageOverlay system={{damage}} message={`${name} Offline`} />
        <CardBody>

{name}

          <Row>
            <Col sm={7}>
              {direction === "unspecified" && docked && (
                <Button
                  block
                  className="departure-button"
                  color="success"
                  onClick={() =>
                    this.updateShuttle(id, "direction", "departing")
                  }
                >
                  Prepare for departure
                </Button>
              )}
              {direction === "departing" && docked && (
                <Button
                  block
                  className="departure-button"
                  color="danger"
                  onClick={() =>
                    this.updateShuttle(id, "direction", "unspecified")
                  }
                >
                  Abort departure sequence
                </Button>
              )}

                <div className={`docking-icon ${hint.clamps}`}>
                  {hintStrings[hint.clamps]}

                <Button
                  block
                  className="clamps-button"
                  disabled={!!animating || !docked}
                  color="primary"
                  onClick={() => this.toggleShuttle(id, "clamps")}
                >
                  Clamps {clamps ? "attached" : "detached"}
                </Button>

                <div className={`docking-icon ${hint.compress}`}>
                  {hintStrings[hint.compress]}

                <Button
                  block
                  disabled={!!animating || !doors}
                  color="primary"
                  className="compress-button"
                  onClick={() => this.toggleShuttle(id, "compress")}
                >
                  {compress ? "Compressed" : "Decompressed"}
                </Button>

                <div className={`docking-icon ${hint.doors}`}>
                  {hintStrings[hint.doors]}

                <Button
                  block
                  disabled={!!animating || compress}
                  color="primary"
                  className="doors-button"
                  onClick={() => this.toggleShuttle(id, "doors")}
                >
                  Doors {doors ? "closed" : "open"}
                </Button>

{shuttleStatusMsg}

            </Col>
            <Col sm={5}>
              {animating === "clamps" && <Clamps transform={clamps} />}
              {animating === "compress" && <Decompress on={compress} />}
              {animating === "doors" && (
                <Door open={!doors} number={"0" + (i + 1)} />
              )}
              {docked ? (
                <div
                  className="picture shuttle"
                  style={{
                    backgroundImage: `url('/assets${image}')`,
                    display: !animating ? "flex" : "none",
                  }}
                >

              ) : (

Shuttlebay Empty

              )}
            </Col>
          </Row>
        </CardBody>
      </Card>
    );
  }
}

export const SHUTTLE_QUERY = gql`
  query Shuttles($simulatorId: ID) {
    docking(simulatorId: $simulatorId, type: shuttlebay) {
      id
      name
      clamps
      compress
      doors
      image
      docked
      damage {
        damaged
      }
      direction
    }
  }
`;
export default graphql(SHUTTLE_QUERY, {
  options: ownProps => ({
    fetchPolicy: "cache-and-network",

    variables: {
      simulatorId: ownProps.simulator.id,
    },
  }),
})(withApollo(Shuttles));
<commit_msg>Improve text length on shuttle screen.<commit_after>import React, {Component} from "react";
import gql from "graphql-tag.macro";
import {graphql, withApollo} from "react-apollo";
import {Container, Row, Col, Card, CardBody, Button} from "helpers/reactstrap";
import Tour from "helpers/tourHelper";
import Decompress from "./Decompress";
import Door from "./Door";
import {Clamps} from "../Docking/graphics";
import "./style.scss";
import SubscriptionHelper from "helpers/subscriptionHelper";
import DamageOverlay from "../helpers/DamageOverlay";
import {FaCheck, FaArrowRight} from "react-icons/fa";

export const SHUTTLE_SUB = gql`
  subscription ShuttlesUpdate($simulatorId: ID) {
    dockingUpdate(simulatorId: $simulatorId, type: shuttlebay) {
      id
      name
      clamps
      compress
      doors
      image
      docked
      damage {
        damaged
      }
      direction
    }
  }
`;

const trainingSteps = [
  {
    selector: ".shuttles-card",
    content:
      "Shuttles are small ships which can be stored inside of your ship. Most shuttles are piloted and can transport large amounts of supplies or personnel great distances.",
  },
  {
    selector: ".departure-button",
    content:
      'To use a shuttle, first click "Prepare for departure" to begin the departure sequence.',
  },
  {
    selector: ".clamps-button",
    content:
      "You must then release the docking clamps which hold the shuttle in place. This button shows whether the clamps are attached or detached.",
  },
  {
    selector: ".compress-button",
    content:
      "Then, you must decompress the shuttlebay. To board the shuttle, there must be air inside the shuttlebay. However, if the shuttlebay doors are opened without decompressing the shuttlebay, all of that air would be sucked into space. The change in pressure would also pull people and equipment into space as well, so decompressing the shuttlebay is an important safety step.",
  },
  {
    selector: ".doors-button",
    content:
      "Finally, you can open the shuttlebay doors. Once the image of the shuttle disappears, you can know that the shuttle has disembarked from your ship.",
  },
  {
    selector: ".nothing",
    content: "To dock a shuttle, follow the steps in the opposite order.",
  },
];
class Shuttles extends Component {
  render() {
    if (this.props.data.loading || !this.props.data.docking) return null;
    const {docking} = this.props.data;
    if (!docking) return null;
    return (
      <Container fluid className="shuttles-card">
        <SubscriptionHelper
          subscribe={() =>
            this.props.data.subscribeToMore({
              document: SHUTTLE_SUB,
              variables: {
                simulatorId: this.props.simulator.id,
              },
              updateQuery: (previousResult, {subscriptionData}) => {
                return Object.assign({}, previousResult, {
                  docking: subscriptionData.data.dockingUpdate,
                });
              },
            })
          }
        />
        {
          <Row>
            {docking.map((d, i) => (

                <ShuttleBay
                  {...d}
                  i={i}
                  client={this.props.client}
                  simulatorId={this.props.simulator.id}
                />

            ))}
          </Row>
        }
        <Tour steps={trainingSteps} client={this.props.clientObj} />
      </Container>
    );
  }
}

class ShuttleBay extends Component {
  state = {animating: null};
  toggleShuttle = (id, which) => {
    this.setState({animating: which});
    const mutation = gql`
      mutation UpdateShuttleBay($port: DockingPortInput!) {
        updateDockingPort(port: $port)
      }
    `;
    const port = {
      id,
    };
    port[which] = !this.props[which];
    this.props.client.mutate({
      mutation,
      variables: {port},
    });
    if (this.clearTimeoutId) clearTimeout(this.clearTimeoutId);
    this.clearTimeoutId = setTimeout(
      () => this.setState({animating: null}),
      4000,
    );
  };
  updateShuttle = (id, which, value) => {
    const mutation = gql`
      mutation UpdateShuttleBay($port: DockingPortInput!) {
        updateDockingPort(port: $port)
      }
    `;
    const port = {
      id,
    };
    port[which] = value;
    this.props.client.mutate({
      mutation,
      variables: {port},
    });
  };
  render() {
    const {
      docked,
      image,
      name,
      i,
      id,
      clamps,
      compress,
      doors,
      damage,
      direction,
    } = this.props;
    const {animating} = this.state;

    let hint = {clamps: "", compress: "", doors: ""};

    let shuttleStatusMsg = "";
    if (direction === "departing" && docked) {
      shuttleStatusMsg = "Preparing shuttle for departure.";

      if (!clamps) hint.clamps = "ok";
      if (!compress) hint.compress = "ok";
      if (!doors) hint.doors = "ok";

      if (clamps) hint.clamps = "attention";
      else if (compress) hint.compress = "attention";
      else if (doors) hint.doors = "attention";
      else shuttleStatusMsg = "Shuttle is departing.";
    } else if (direction === "departing" && !docked) {
      shuttleStatusMsg = "Shuttle has departed. Secure shuttle bay.";

      if (!clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";

      if (!doors) hint.doors = "attention";
      else if (!compress) hint.compress = "attention";
      else shuttleStatusMsg = "Shuttle bay secured.";
    } else if (direction === "arriving" && !docked) {
      shuttleStatusMsg = "Shuttle approaching. Prepare for arrival.";

      if (!clamps) hint.clamps = "ok";
      if (!compress) hint.compress = "ok";
      if (!doors) hint.doors = "ok";

      if (clamps) hint.clamps = "attention";
      else if (compress) hint.compress = "attention";
      else if (doors) hint.doors = "attention";
      else shuttleStatusMsg = "The shuttle is entering the bay. Please wait.";
    } else if (direction === "arriving" && docked) {
      shuttleStatusMsg = "Shuttle has arrived. Please secure shuttle.";

      if (clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";

      if (!doors) hint.doors = "attention";
      else if (!compress) hint.compress = "attention";
      else if (!clamps) hint.clamps = "attention";
      else shuttleStatusMsg = "Shuttle secured.";
    } else if (!docked) {
      if (!clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";
    } else if (docked) {
      if (clamps) hint.clamps = "ok";
      if (compress) hint.compress = "ok";
      if (doors) hint.doors = "ok";
    }

    var hintStrings = {
      ok: <FaCheck />,
      attention: <FaArrowRight />,
    };

    return (
      <Card>
        <DamageOverlay system={{damage}} message={`${name} Offline`} />
        <CardBody>

{name}

          <Row>
            <Col sm={7}>
              {direction === "unspecified" && docked && (
                <Button
                  block
                  className="departure-button"
                  color="success"
                  onClick={() =>
                    this.updateShuttle(id, "direction", "departing")
                  }
                >
                  Prepare to Depart
                </Button>
              )}
              {direction === "departing" && docked && (
                <Button
                  block
                  className="departure-button"
                  color="danger"
                  onClick={() =>
                    this.updateShuttle(id, "direction", "unspecified")
                  }
                >
                  Abort Departure
                </Button>
              )}

                <div className={`docking-icon ${hint.clamps}`}>
                  {hintStrings[hint.clamps]}

                <Button
                  block
                  className="clamps-button"
                  disabled={!!animating || !docked}
                  color="primary"
                  onClick={() => this.toggleShuttle(id, "clamps")}
                >
                  Clamps {clamps ? "attached" : "detached"}
                </Button>

                <div className={`docking-icon ${hint.compress}`}>
                  {hintStrings[hint.compress]}

                <Button
                  block
                  disabled={!!animating || !doors}
                  color="primary"
                  className="compress-button"
                  onClick={() => this.toggleShuttle(id, "compress")}
                >
                  {compress ? "Compressed" : "Decompressed"}
                </Button>

                <div className={`docking-icon ${hint.doors}`}>
                  {hintStrings[hint.doors]}

                <Button
                  block
                  disabled={!!animating || compress}
                  color="primary"
                  className="doors-button"
                  onClick={() => this.toggleShuttle(id, "doors")}
                >
                  Doors {doors ? "closed" : "open"}
                </Button>

{shuttleStatusMsg}

            </Col>
            <Col sm={5}>
              {animating === "clamps" && <Clamps transform={clamps} />}
              {animating === "compress" && <Decompress on={compress} />}
              {animating === "doors" && (
                <Door open={!doors} number={"0" + (i + 1)} />
              )}
              {docked ? (
                <div
                  className="picture shuttle"
                  style={{
                    backgroundImage: `url('/assets${image}')`,
                    display: !animating ? "flex" : "none",
                  }}
                >

              ) : (

Shuttlebay Empty

              )}
            </Col>
          </Row>
        </CardBody>
      </Card>
    );
  }
}

export const SHUTTLE_QUERY = gql`
  query Shuttles($simulatorId: ID) {
    docking(simulatorId: $simulatorId, type: shuttlebay) {
      id
      name
      clamps
      compress
      doors
      image
      docked
      damage {
        damaged
      }
      direction
    }
  }
`;
export default graphql(SHUTTLE_QUERY, {
  options: ownProps => ({
    fetchPolicy: "cache-and-network",

    variables: {
      simulatorId: ownProps.simulator.id,
    },
  }),
})(withApollo(Shuttles));
<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's ID, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The element to create. Defaults to "input".
        // To define options for a select, use <Field><option ... /></Field>
        element: PropTypes.oneOf(["input", "select", "textarea"]),
        // The field's type (when used as an <input>). Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string. Defaults to the label.
        placeholder: PropTypes.string,
        // Optional component to include inside the field before the input.
        prefix: PropTypes.node,
        // The callback called whenever the contents of the field
        // changes.  Returns an object with `valid` boolean field
        // and a `feedback` react component field to provide feedback
        // to the user.
        onValidate: PropTypes.function,
        // All other props pass through to the <input>.
    };

    constructor() {
        super();
        this.state = {
            valid: undefined,
            feedback: undefined,
        };
    }

    get value() {
        if (!this.refs.fieldInput) return null;
        return this.refs.fieldInput.value;
    }

    set value(newValue) {
        if (!this.refs.fieldInput) {
            throw new Error("No field input reference");
        }
        this.refs.fieldInput.value = newValue;
    }

    onChange = (ev) => {
        if (this.props.onValidate) {
            const result = this.props.onValidate(this.value);
            this.setState({
                valid: result.valid,
                feedback: result.feedback,
            });
        }
        // Parent component may have supplied its own `onChange` as well
        if (this.props.onChange) {
            this.props.onChange(ev);
        }
    };

    render() {
        const { element, prefix, onValidate, children, ...inputProps } = this.props;

        const inputElement = element || "input";

        // Set some defaults for the <input> element
        inputProps.type = inputProps.type || "text";
        inputProps.ref = "fieldInput";
        inputProps.placeholder = inputProps.placeholder || inputProps.label;

        inputProps.onChange = this.onChange;
        // make sure we use the current `value` for the field and not the original one
        if (this.value != undefined) {
            inputProps.value = this.value;
        }

        const fieldInput = React.createElement(inputElement, inputProps, children);

        let prefixContainer = null;
        if (prefix) {
            prefixContainer = {prefix};
        }

        let validClass;
        if (onValidate) {
            validClass = classNames({
                mx_Field_valid: this.state.valid === true,
                mx_Field_invalid: this.state.valid === false,
            });
        }

        const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
            // If we have a prefix element, leave the label always at the top left and
            // don't animate it, as it looks a bit clunky and would add complexity to do
            // properly.
            mx_Field_labelAlwaysTopLeft: prefix,
            [validClass]: true,
        });

        // handle displaying feedback on validity
        const Tooltip = sdk.getComponent("elements.Tooltip");
        let feedback;
        if (this.state.feedback) {
            feedback = <Tooltip
                tooltipClassName="mx_Field_tooltip"
                label={this.state.feedback}
            />;
        }

        return

            {prefixContainer}
            {fieldInput}
            {this.props.label}
            {feedback}

;
    }
}
<commit_msg>Use the right prop type for functions<commit_after>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's ID, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The element to create. Defaults to "input".
        // To define options for a select, use <Field><option ... /></Field>
        element: PropTypes.oneOf(["input", "select", "textarea"]),
        // The field's type (when used as an <input>). Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string. Defaults to the label.
        placeholder: PropTypes.string,
        // Optional component to include inside the field before the input.
        prefix: PropTypes.node,
        // The callback called whenever the contents of the field
        // changes.  Returns an object with `valid` boolean field
        // and a `feedback` react component field to provide feedback
        // to the user.
        onValidate: PropTypes.func,
        // All other props pass through to the <input>.
    };

    constructor() {
        super();
        this.state = {
            valid: undefined,
            feedback: undefined,
        };
    }

    get value() {
        if (!this.refs.fieldInput) return null;
        return this.refs.fieldInput.value;
    }

    set value(newValue) {
        if (!this.refs.fieldInput) {
            throw new Error("No field input reference");
        }
        this.refs.fieldInput.value = newValue;
    }

    onChange = (ev) => {
        if (this.props.onValidate) {
            const result = this.props.onValidate(this.value);
            this.setState({
                valid: result.valid,
                feedback: result.feedback,
            });
        }
        // Parent component may have supplied its own `onChange` as well
        if (this.props.onChange) {
            this.props.onChange(ev);
        }
    };

    render() {
        const { element, prefix, onValidate, children, ...inputProps } = this.props;

        const inputElement = element || "input";

        // Set some defaults for the <input> element
        inputProps.type = inputProps.type || "text";
        inputProps.ref = "fieldInput";
        inputProps.placeholder = inputProps.placeholder || inputProps.label;

        inputProps.onChange = this.onChange;
        // make sure we use the current `value` for the field and not the original one
        if (this.value != undefined) {
            inputProps.value = this.value;
        }

        const fieldInput = React.createElement(inputElement, inputProps, children);

        let prefixContainer = null;
        if (prefix) {
            prefixContainer = {prefix};
        }

        let validClass;
        if (onValidate) {
            validClass = classNames({
                mx_Field_valid: this.state.valid === true,
                mx_Field_invalid: this.state.valid === false,
            });
        }

        const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
            // If we have a prefix element, leave the label always at the top left and
            // don't animate it, as it looks a bit clunky and would add complexity to do
            // properly.
            mx_Field_labelAlwaysTopLeft: prefix,
            [validClass]: true,
        });

        // handle displaying feedback on validity
        const Tooltip = sdk.getComponent("elements.Tooltip");
        let feedback;
        if (this.state.feedback) {
            feedback = <Tooltip
                tooltipClassName="mx_Field_tooltip"
                label={this.state.feedback}
            />;
        }

        return

            {prefixContainer}
            {fieldInput}
            {this.props.label}
            {feedback}

;
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's ID, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The element to create. Defaults to "input".
        // To define options for a select, use <Field><option ... /></Field>
        element: PropTypes.oneOf(["input", "select", "textarea"]),
        // The field's type (when used as an <input>). Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string. Defaults to the label.
        placeholder: PropTypes.string,
        // The field's value.
        // This is a controlled component, so the value is required.
        value: PropTypes.string.isRequired,
        // Optional component to include inside the field before the input.
        prefix: PropTypes.node,
        // The callback called whenever the contents of the field
        // changes.  Returns an object with `valid` boolean field
        // and a `feedback` react component field to provide feedback
        // to the user.
        onValidate: PropTypes.func,
        // All other props pass through to the <input>.
    };

    constructor() {
        super();
        this.state = {
            valid: undefined,
            feedback: undefined,
        };
    }

    onChange = (ev) => {
        if (this.props.onValidate) {
            const result = this.props.onValidate(ev.target.value);
            this.setState({
                valid: result.valid,
                feedback: result.feedback,
            });
        }
        // Parent component may have supplied its own `onChange` as well
        if (this.props.onChange) {
            this.props.onChange(ev);
        }
    };

    render() {
        const { element, prefix, onValidate, children, ...inputProps } = this.props;

        const inputElement = element || "input";

        // Set some defaults for the <input> element
        inputProps.type = inputProps.type || "text";
        inputProps.ref = "fieldInput";
        inputProps.placeholder = inputProps.placeholder || inputProps.label;

        inputProps.onChange = this.onChange;

        const fieldInput = React.createElement(inputElement, inputProps, children);

        let prefixContainer = null;
        if (prefix) {
            prefixContainer = {prefix};
        }

        let validClass;
        if (onValidate) {
            validClass = classNames({
                mx_Field_valid: this.state.valid === true,
                mx_Field_invalid: this.state.valid === false,
            });
        }

        const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
            // If we have a prefix element, leave the label always at the top left and
            // don't animate it, as it looks a bit clunky and would add complexity to do
            // properly.
            mx_Field_labelAlwaysTopLeft: prefix,
            [validClass]: true,
        });

        // handle displaying feedback on validity
        const Tooltip = sdk.getComponent("elements.Tooltip");
        let feedback;
        if (this.state.feedback) {
            feedback = <Tooltip
                tooltipClassName="mx_Field_tooltip"
                label={this.state.feedback}
            />;
        }

        return

            {prefixContainer}
            {fieldInput}
            {this.props.label}
            {feedback}

;
    }
}
<commit_msg>Fix validation to avoid `undefined` class on fields<commit_after>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's ID, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The element to create. Defaults to "input".
        // To define options for a select, use <Field><option ... /></Field>
        element: PropTypes.oneOf(["input", "select", "textarea"]),
        // The field's type (when used as an <input>). Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string. Defaults to the label.
        placeholder: PropTypes.string,
        // The field's value.
        // This is a controlled component, so the value is required.
        value: PropTypes.string.isRequired,
        // Optional component to include inside the field before the input.
        prefix: PropTypes.node,
        // The callback called whenever the contents of the field
        // changes.  Returns an object with `valid` boolean field
        // and a `feedback` react component field to provide feedback
        // to the user.
        onValidate: PropTypes.func,
        // All other props pass through to the <input>.
    };

    constructor() {
        super();
        this.state = {
            valid: undefined,
            feedback: undefined,
        };
    }

    onChange = (ev) => {
        if (this.props.onValidate) {
            const result = this.props.onValidate(ev.target.value);
            this.setState({
                valid: result.valid,
                feedback: result.feedback,
            });
        }
        // Parent component may have supplied its own `onChange` as well
        if (this.props.onChange) {
            this.props.onChange(ev);
        }
    };

    render() {
        const { element, prefix, onValidate, children, ...inputProps } = this.props;

        const inputElement = element || "input";

        // Set some defaults for the <input> element
        inputProps.type = inputProps.type || "text";
        inputProps.ref = "fieldInput";
        inputProps.placeholder = inputProps.placeholder || inputProps.label;

        inputProps.onChange = this.onChange;

        const fieldInput = React.createElement(inputElement, inputProps, children);

        let prefixContainer = null;
        if (prefix) {
            prefixContainer = {prefix};
        }

        const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
            // If we have a prefix element, leave the label always at the top left and
            // don't animate it, as it looks a bit clunky and would add complexity to do
            // properly.
            mx_Field_labelAlwaysTopLeft: prefix,
            mx_Field_valid: onValidate && this.state.valid === true,
            mx_Field_invalid: onValidate && this.state.valid === false,
        });

        // handle displaying feedback on validity
        const Tooltip = sdk.getComponent("elements.Tooltip");
        let feedback;
        if (this.state.feedback) {
            feedback = <Tooltip
                tooltipClassName="mx_Field_tooltip"
                label={this.state.feedback}
            />;
        }

        return

            {prefixContainer}
            {fieldInput}
            {this.props.label}
            {feedback}

;
    }
}
<|endoftext|>


<commit_before>const SubscriptionState = require('./subscriptionState')

describe('Consumer > OffsetMananger > pause / resume', () => {
  let subscriptionState
  const byTopic = (a, b) => a.topic - b.topic

  beforeEach(() => {
    subscriptionState = new SubscriptionState()
  })

  it('pauses the selected topics', () => {
    subscriptionState.pause([{ topic: 'topic1' }, { topic: 'topic2' }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [], all: true },
      { topic: 'topic2', partitions: [], all: true },
    ])
  })

  it('resumes the selected topics', () => {
    subscriptionState.pause([{ topic: 'topic1' }, { topic: 'topic2' }])
    subscriptionState.resume([{ topic: 'topic2' }])

    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [], all: true },
      { topic: 'topic2', partitions: [], all: false },
    ])
  })

  it('pauses the selected partitions', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0, 1], all: false },
    ])

    subscriptionState.pause([{ topic: 'topic1', partitions: [1, 2] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0, 1, 2], all: false },
    ])
  })

  it('resumes the selected partitions', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }])
    subscriptionState.resume([{ topic: 'topic1', partitions: [1] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0], all: false },
    ])

    subscriptionState.resume([{ topic: 'topic1', partitions: [2] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0], all: false },
    ])
  })
})

describe('Consumer > OffsetMananger > isPaused', () => {
  let subscriptionState
  const byTopic = (a, b) => a.topic - b.topic

  beforeEach(() => {
    subscriptionState = new SubscriptionState()
  })

  it('can determine whether a topic partition is paused', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }, { topic: 'topic2' }])

    expect(subscriptionState.isPaused('topic1', 0)).toEqual(true)
    expect(subscriptionState.isPaused('topic1', 2)).toEqual(false)
    expect(subscriptionState.isPaused('topic2', 0)).toEqual(true)
    expect(subscriptionState.isPaused('topic2', 2)).toEqual(true)
    expect(subscriptionState.isPaused('unknown', 0)).toEqual(false)
  })
})
<commit_msg>Remove unused code from subscription state test<commit_after>const SubscriptionState = require('./subscriptionState')

describe('Consumer > OffsetMananger > pause / resume', () => {
  let subscriptionState
  const byTopic = (a, b) => a.topic - b.topic

  beforeEach(() => {
    subscriptionState = new SubscriptionState()
  })

  it('pauses the selected topics', () => {
    subscriptionState.pause([{ topic: 'topic1' }, { topic: 'topic2' }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [], all: true },
      { topic: 'topic2', partitions: [], all: true },
    ])
  })

  it('resumes the selected topics', () => {
    subscriptionState.pause([{ topic: 'topic1' }, { topic: 'topic2' }])
    subscriptionState.resume([{ topic: 'topic2' }])

    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [], all: true },
      { topic: 'topic2', partitions: [], all: false },
    ])
  })

  it('pauses the selected partitions', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0, 1], all: false },
    ])

    subscriptionState.pause([{ topic: 'topic1', partitions: [1, 2] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0, 1, 2], all: false },
    ])
  })

  it('resumes the selected partitions', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }])
    subscriptionState.resume([{ topic: 'topic1', partitions: [1] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0], all: false },
    ])

    subscriptionState.resume([{ topic: 'topic1', partitions: [2] }])
    expect(subscriptionState.paused().sort(byTopic)).toEqual([
      { topic: 'topic1', partitions: [0], all: false },
    ])
  })
})

describe('Consumer > OffsetMananger > isPaused', () => {
  let subscriptionState

  beforeEach(() => {
    subscriptionState = new SubscriptionState()
  })

  it('can determine whether a topic partition is paused', () => {
    subscriptionState.pause([{ topic: 'topic1', partitions: [0, 1] }, { topic: 'topic2' }])

    expect(subscriptionState.isPaused('topic1', 0)).toEqual(true)
    expect(subscriptionState.isPaused('topic1', 2)).toEqual(false)
    expect(subscriptionState.isPaused('topic2', 0)).toEqual(true)
    expect(subscriptionState.isPaused('topic2', 2)).toEqual(true)
    expect(subscriptionState.isPaused('unknown', 0)).toEqual(false)
  })
})
<|endoftext|>