<commit_before>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

//!!! ...unfinished...
// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// Solution may use classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.

import * as Net from "./Net.js";
import * as WorkerProxy from "./WorkerProxy.js";

/**
 * The implementation of a neural network web worker.
 *
 * Many workers cascade in chain. Every worker handles one neural network. When apply() is called, the input (usually a large memory block)
 * will be transffered to the 1st worker to start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring (not by copying). If the large data wants to be transferred
 * to many workers, the only possible way is to transferring them serially. This is why the workers are arranged in cascade chain.
 *
 * However, serially transferring hurts the performance. Workers are better to compute parallelly. So every worker should transfer data to next
 * worker as soon as possible. When they get the first part of calculation result (rather than wait for all calculation done), they should
 * transfer the input data to the next worker immediately.
 *
 */
class WorkerBody {

  /**
   * If ( ( totalWorkerCount - workerId ) > 0 ), this method will create a neural network.
   * If ( ( totalWorkerCount - workerId ) > 1 ), this method will create the next neural network web worker, too.
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {number} totalWorkerCount
   *   There are how many workers should be created in chain.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId, neuralNetConfig, totalWorkerCount, weightsURL ) {

    workerId = workerId | 0;
    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let remainedWorkerCount = totalWorkerCount - workerId; // How many web worker should be created.
    if ( remainedWorkerCount <= 0 )
      return; // Should not happen. If happened, just return without creating any neural network.

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

    this.neuralNet = new Net();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

    remainedWorkerCount -= 1; // "-1" for a neural network is created.
    if ( remainedWorkerCount <= 0 )
      return; // All neural network web worker are created.

    let nextWorkerId = workerId + 1;
    let nextWorkerProxy = this.nextWorkerProxy = new WorkerProxy.Base();
    nextWorkerProxy.init( nextWorkerId, neuralNetConfig, totalWorkerCount, weightsURL ); // Create the next web worker in cascade chain.
  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

    if ( this.nextWorkerProxy ) { // Dispose the next web worker in cascade chain.
      this.nextWorkerProxy.disposeWorker();
      this.nextWorkerProxy = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

  /**
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing result.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed.
   *
???
   * @param {tf.tensor3d[]} resultArray
   *   If ( resultArray != null ), all result (new) tensors will be filled into this array. This could reduce the array memory
   * re-allocation and improve performance. If ( resultArray == null ), all result tensors will be disposed and nothing will be
   * returned. No matter in which case, all other intermediate tensors were disposed.
   *
   * @return {Promise} Return a promise which resolves with the resultArray.
   */
  processTensor( processingId, sourceImageData ) {

//!!! ...unfinished...
    let resultTypedArray = resultTensor.data();

    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );
  }

}

if ( globalThis.document ) {
  // In main document context (Not in worker context). Do nothing.

} else {
  // In worker context. Register message handler.

  globalThis.workerBody = new WorkerBody();

  globalThis.onmessage = function( e ) {
    let message = e.data;

    switch ( message.command ) {
      case "init": //{ command: "init", workerId, neuralNetConfig, totalWorkerCount, weightsURL };
        globalThis.workerProxy.init( message.workerId, message.neuralNetConfig, message.totalWorkerCount, message.weightsURL );
        break;

      case "disposeWorker": //{ command: "disposeWorker" };
        globalThis.workerProxy.disposeWorker();
        break;

      case "processTensor": //{ command: "processTensor", processingId, sourceImageData };
        globalThis.workerProxy.processTensor( message.processingId, message.sourceImageData );
        break;

    }
  }
}
<commit_msg>Update WorkerBody.js<commit_after>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

//!!! ...unfinished...
// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// Solution may use classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.

import * as Net from "./Net.js";
import * as WorkerProxy from "./WorkerProxy.js";

/**
 * The implementation of a neural network web worker.
 *
 * Many workers cascade in chain. Every worker handles one neural network. When apply() is called, the input (usually a large memory block)
 * will be transffered to the 1st worker to start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring (not by copying). If the large data wants to be transferred
 * to many workers, the only possible way is to transferring them serially. This is why the workers are arranged in cascade chain.
 *
 * However, serially transferring hurts the performance. Workers are better to compute parallelly. So every worker should transfer data to next
 * worker as soon as possible. When they get the first part of calculation result (rather than wait for all calculation done), they should
 * transfer the input data to the next worker immediately.
 *
 */
class WorkerBody {

  /**
   * If ( ( totalWorkerCount - workerId ) > 0 ), this method will create a neural network.
   * If ( ( totalWorkerCount - workerId ) > 1 ), this method will create the next neural network web worker, too.
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {number} totalWorkerCount
   *   There are how many workers should be created in chain.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId, neuralNetConfig, totalWorkerCount, weightsURL ) {

    workerId = workerId | 0;
    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let remainedWorkerCount = totalWorkerCount - workerId; // How many web worker should be created.
    if ( remainedWorkerCount <= 0 )
      return; // Should not happen. If happened, just return without creating any neural network.

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

    this.neuralNet = new Net();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

    remainedWorkerCount -= 1; // "-1" for a neural network is created.
    if ( remainedWorkerCount <= 0 )
      return; // All neural network web worker are created.

    let nextWorkerId = workerId + 1;
    let nextWorkerProxy = this.nextWorkerProxy = new WorkerProxy.Base();
    nextWorkerProxy.init( nextWorkerId, neuralNetConfig, totalWorkerCount, weightsURL ); // Create the next web worker in cascade chain.
  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

    if ( this.nextWorkerProxy ) { // Dispose the next web worker in cascade chain.
      this.nextWorkerProxy.disposeWorker();
      this.nextWorkerProxy = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

  /**
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing result.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed.
   *
???
   * @param {tf.tensor3d[]} resultArray
   *   If ( resultArray != null ), all result (new) tensors will be filled into this array. This could reduce the array memory
   * re-allocation and improve performance. If ( resultArray == null ), all result tensors will be disposed and nothing will be
   * returned. No matter in which case, all other intermediate tensors were disposed.
   *
   * @return {Promise} Return a promise which resolves with the resultArray.
   */
  processTensor( processingId, sourceImageData ) {

//!!! ...unfinished...
    let resultTypedArray = resultTensor.data();

    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );

//!!! ...unfinished... The result tensor should be disposed.
  }

}

if ( globalThis.document ) {
  // In main document context (Not in worker context). Do nothing.

} else {
  // In worker context. Register message handler.

  globalThis.workerBody = new WorkerBody();

  globalThis.onmessage = function( e ) {
    let message = e.data;

    switch ( message.command ) {
      case "init": //{ command: "init", workerId, neuralNetConfig, totalWorkerCount, weightsURL };
        globalThis.workerProxy.init( message.workerId, message.neuralNetConfig, message.totalWorkerCount, message.weightsURL );
        break;

      case "disposeWorker": //{ command: "disposeWorker" };
        globalThis.workerProxy.disposeWorker();
        break;

      case "processTensor": //{ command: "processTensor", processingId, sourceImageData };
        globalThis.workerProxy.processTensor( message.processingId, message.sourceImageData );
        break;

    }
  }
}
<|endoftext|>


<commit_before>"use strict";
/* eslint-env node */
module.exports = {
  extends: ["eslint:recommended", "prettier", "prettier/react"],
  parserOptions: {
    sourceType: "module",
    ecmaVersion: "2020",
    ecmaFeatures: {
      jsx: true
    }
  },
  env: {
    es6: true,
    browser: true
  },
  plugins: ["prettier"],
  rules: {
    "prettier/prettier": "error"
  }
};
<commit_msg>Fixup lint errors in linter config<commit_after>"use strict";
/* eslint-env node */
module.exports = {
  extends: ["eslint:recommended", "prettier", "prettier/react"],
  parserOptions: {
    sourceType: "module",
    ecmaVersion: "2020",
    ecmaFeatures: {
      jsx: true,
    },
  },
  env: {
    es6: true,
    browser: true,
  },
  plugins: ["prettier"],
  rules: {
    "prettier/prettier": "error",
  },
};
<|endoftext|>


<commit_before>module.exports = {
    "rules": {
        "indent": [
            2,
            4
        ],
        "quotes": [
            2,
            "single"
        ],
        "linebreak-style": [
            2,
            "unix"
        ],
        "semi": [
            2,
            "always"
        ]
    },
    "env": {
        "es6": true,
        "node": true
    },
    "extends": "eslint:recommended"
};<commit_msg>modify eslintrc.js<commit_after>module.exports = {
    'rules': {
        'indent': [2, 4],
        'quotes': [2, 'single'],
        'linebreak-style': [2, 'unix'],
        'semi': [2, 'always'],
        'comma-dangle': [2, 'always-multiline'],
        'no-console': 0,
    },
    'env': {
        'es6': true,
        'node': true,
    },
    'extends': 'eslint:recommended',
};<|endoftext|>


<commit_before>//= require ./redactor
//= require ./config
<commit_msg>Update script loading to prefer an in-project config.js if available<commit_after>//= require ./redactor
//= require ./config-setup
//= require refinery-redactor/config
//= require ./boot
<|endoftext|>


<commit_before>/*
 * livedata_connection.js - Configure clients to work in a distributed server
 *                          environment where the user might be connected with
 *                          the wrong server.
 */

// If we have a logged in user, userReplicaUrls should be set to be the array
// of replicas storing the user's data.
Meteor.userReplicaUrls = [];

// If we have connected to a Meteor server based on the logged in user,
// userCoordinatorUrl should be set to the host name of the connected server.
// At the time of assignment, it should be a member of userReplicaUrls.
Meteor.userCoordinatorUrl = "";

// Connect Meteor to a new server at the given url.
function connect (url) {

  // Clearing userCoordinatorUrl before disconnecting and reconnecting prevents
  // our disconnect handler (see below) from running during this manual
  // disconnect. This effectively locks this connect() method and the body of
  // our disconnect handler. Not doing so leads to an unbounded number of calls
  // to connect(url) (with the same url) and an eventual stack overflow error.
  // DO NOT REMOVE THIS LINE!
  Meteor.userCoordinatorUrl = "";

  // Clear the connection retryCount to 0.
  // This can also be accomplished by disconnecting before reconnecting, but
  // that causes race conditions.
  Meteor.connection._stream.currentStatus.retryCount = 0;

  // Reconnect to the new url.
  // The Meteor.connection object is mutated, not replaced, so all objects that
  // share the same reference (including the global Accounts object and all
  // defined collections) will also have the new connection.
  Meteor.connection._stream.reconnect({url: url});
  Meteor.userCoordinatorUrl = url;
}

// Save a correctly bound reference to Meteor.connection.apply() before we
// redefine it.
var apply = Meteor.bindEnvironment(Meteor.connection.apply,
                                   undefined, Meteor.connection);

// Wraps Meteor.apply() to handle WrongConnectionErrors.
//
// When an apply() is attempted, the server may reply with
// WrongConnectionError, indicating that it is not a replica for the current
// user. The client should cache the list of replicas, connect to the first
// one, and re-send the apply().
Meteor.connection.apply = function (name, args, options, callback, meta) {
  // Deal with the possibility of receiving less than five arguments.  Copied
  // from the implementation of apply() in
  // packages/livedata/livedata_connection.js in the mylar source.
  if (!callback && typeof options === 'function') {
    callback = options;
    options = {};
  }
  options = options || {};
  if (callback) {
    callback = Meteor.bindEnvironment(
      callback,
      "delivering result of invoking '" + name + "'"
    );
  }

  // A wrapped callback to handle WrongConnectionErrors.
  var connectionUpdateCallback = function (error, result) {
    error = CastErrorToCorrectSubclass(error);
    if (error && (error instanceof Meteor.WrongConnectionError)) {
      Meteor.userReplicaUrls = error.userReplicaUrls;
      connect(error.userReplicaUrls[0]);

      // Recurse after changing connection.
      Meteor.connection.apply(name, args, options, callback, meta);
    }
    else if (callback) {
      callback(error, result);
    }
  }

  // Call original apply(), but with wrapped callback.
  apply(name, args, options, connectionUpdateCallback, meta);
}

// If the connected server becomes unreachable and we know what replicas are
// storing the current user's data, establish a connection with one of the
// other replicas.
Meteor.connection._stream.on('disconnect', function () {
  // Only switch replicas if we are not currently performing a
  // disconnect/reconnect (see the comments in the definition of connect()), if
  // we have a list of replicas to choose from, and if we have tried connecting
  // to the current server at least twice.
  if ( Meteor.userCoordinatorUrl &&
       Meteor.userReplicaUrls.length > 0 &&
       Meteor.status().retryCount > 1 ) {
    // Choose the next replica in the list, circling around if we've reached
    // the end, and starting at the beginning if the current server is not in
    // the list (i.e., if indexOf() returns -1).
    // Each failed connection try actually triggers a disconnect event. So
    // there is no need to do any iteration in this handler (in fact, it
    // wouldn't even work). This handler will be called repeatedly until a
    // suitable connection has been made.
    var i = 1 + _.indexOf(Meteor.userReplicaUrls, Meteor.userCoordinatorUrl);
    i = i % Meteor.userReplicaUrls.length;
    connect(Meteor.userReplicaUrls[i]);
  }
});

// When the client is loaded, Meteor.userReplicaUrls is computed based on
// the current user. And since Meteor.userId() is reactive, it is recomputed
// whenever the current user changes.
Deps.autorun(function () {
  Meteor.apply('getUserReplicaUrls',
               [Meteor.userId()],
               {wait: true},
               function (error, result) {
    if (result && !error) {
      Meteor.userReplicaUrls = result;
      if (result.length > 0) {
        // If we are not currently connected to a coordinator, make a new
        // connection immediately.
        if (!Meteor.userCoordinatorUrl) {
          connect(result[0]);
        }
      }
      else {
        Meteor.userCoordinatorUrl = "";
      }
    }
  });
});

var _getUserReplicaUrls = function (userId, callback) {
  Meteor.apply('getUserReplicaUrls', [userId], callback);
}

_getUserReplicaUrls = Meteor._wrapAsync(_getUserReplicaUrls);

getUserReplicaUrls = function (userId) {
  return _getUserReplicaUrls(userId);
}
<commit_msg>disable reconnect on login<commit_after>/*
 * livedata_connection.js - Configure clients to work in a distributed server
 *                          environment where the user might be connected with
 *                          the wrong server.
 */

// If we have a logged in user, userReplicaUrls should be set to be the array
// of replicas storing the user's data.
Meteor.userReplicaUrls = [];

// If we have connected to a Meteor server based on the logged in user,
// userCoordinatorUrl should be set to the host name of the connected server.
// At the time of assignment, it should be a member of userReplicaUrls.
Meteor.userCoordinatorUrl = "";

// Connect Meteor to a new server at the given url.
function connect (url) {

  // Clearing userCoordinatorUrl before disconnecting and reconnecting prevents
  // our disconnect handler (see below) from running during this manual
  // disconnect. This effectively locks this connect() method and the body of
  // our disconnect handler. Not doing so leads to an unbounded number of calls
  // to connect(url) (with the same url) and an eventual stack overflow error.
  // DO NOT REMOVE THIS LINE!
  Meteor.userCoordinatorUrl = "";

  // Clear the connection retryCount to 0.
  // This can also be accomplished by disconnecting before reconnecting, but
  // that causes race conditions.
  Meteor.connection._stream.currentStatus.retryCount = 0;

  // Reconnect to the new url.
  // The Meteor.connection object is mutated, not replaced, so all objects that
  // share the same reference (including the global Accounts object and all
  // defined collections) will also have the new connection.
  Meteor.connection._stream.reconnect({url: url});
  Meteor.userCoordinatorUrl = url;
}

// Save a correctly bound reference to Meteor.connection.apply() before we
// redefine it.
var apply = Meteor.bindEnvironment(Meteor.connection.apply,
                                   undefined, Meteor.connection);

// Wraps Meteor.apply() to handle WrongConnectionErrors.
//
// When an apply() is attempted, the server may reply with
// WrongConnectionError, indicating that it is not a replica for the current
// user. The client should cache the list of replicas, connect to the first
// one, and re-send the apply().
Meteor.connection.apply = function (name, args, options, callback, meta) {
  // Deal with the possibility of receiving less than five arguments.  Copied
  // from the implementation of apply() in
  // packages/livedata/livedata_connection.js in the mylar source.
  if (!callback && typeof options === 'function') {
    callback = options;
    options = {};
  }
  options = options || {};
  if (callback) {
    callback = Meteor.bindEnvironment(
      callback,
      "delivering result of invoking '" + name + "'"
    );
  }

  // A wrapped callback to handle WrongConnectionErrors.
  var connectionUpdateCallback = function (error, result) {
    error = CastErrorToCorrectSubclass(error);
    if (error && (error instanceof Meteor.WrongConnectionError)) {
      Meteor.userReplicaUrls = error.userReplicaUrls;
      connect(error.userReplicaUrls[0]);

      // Recurse after changing connection.
      Meteor.connection.apply(name, args, options, callback, meta);
    }
    else if (callback) {
      callback(error, result);
    }
  }

  // Call original apply(), but with wrapped callback.
  apply(name, args, options, connectionUpdateCallback, meta);
}

// If the connected server becomes unreachable and we know what replicas are
// storing the current user's data, establish a connection with one of the
// other replicas.
Meteor.connection._stream.on('disconnect', function () {
  // Only switch replicas if we are not currently performing a
  // disconnect/reconnect (see the comments in the definition of connect()), if
  // we have a list of replicas to choose from, and if we have tried connecting
  // to the current server at least twice.
  if ( Meteor.userCoordinatorUrl &&
       Meteor.userReplicaUrls.length > 0 &&
       Meteor.status().retryCount > 1 ) {
    // Choose the next replica in the list, circling around if we've reached
    // the end, and starting at the beginning if the current server is not in
    // the list (i.e., if indexOf() returns -1).
    // Each failed connection try actually triggers a disconnect event. So
    // there is no need to do any iteration in this handler (in fact, it
    // wouldn't even work). This handler will be called repeatedly until a
    // suitable connection has been made.
    var i = 1 + _.indexOf(Meteor.userReplicaUrls, Meteor.userCoordinatorUrl);
    i = i % Meteor.userReplicaUrls.length;
    connect(Meteor.userReplicaUrls[i]);
  }
});

// When the client is loaded, Meteor.userReplicaUrls is computed based on
// the current user. And since Meteor.userId() is reactive, it is recomputed
// whenever the current user changes.
/*
Deps.autorun(function () {
  Meteor.apply('getUserReplicaUrls',
               [Meteor.userId()],
               {wait: true},
               function (error, result) {
    if (result && !error) {
      Meteor.userReplicaUrls = result;
      if (result.length > 0) {
        // If we are not currently connected to a coordinator, make a new
        // connection immediately.
        if (!Meteor.userCoordinatorUrl) {
          connect(result[0]);
        }
      }
      else {
        Meteor.userCoordinatorUrl = "";
      }
    }
  });
});*/

var _getUserReplicaUrls = function (userId, callback) {
  Meteor.apply('getUserReplicaUrls', [userId], callback);
}

_getUserReplicaUrls = Meteor._wrapAsync(_getUserReplicaUrls);

getUserReplicaUrls = function (userId) {
  return _getUserReplicaUrls(userId);
}
<|endoftext|>


<commit_before>/* global Uint8Array Promise */
const tap = require('tap');
const {AudioContext} = require('web-audio-test-api');

const AudioEngine = require('../src/AudioEngine');

tap.test('SoundPlayer', suite => {

    let audioContext;
    let audioEngine;
    let soundPlayer;

    const help = {
        get engineInputs () {
            return audioEngine.inputNode.toJSON().inputs;
        }
    };

    suite.beforeEach(async () => {
        audioContext = new AudioContext();
        audioEngine = new AudioEngine(audioContext);
        // sound will be 0.1 seconds long
        audioContext.DECODE_AUDIO_DATA_RESULT = audioContext.createBuffer(2, 4410, 44100);
        audioContext.DECODE_AUDIO_DATA_FAILED = false;
        const data = new Uint8Array(44100);
        soundPlayer = await audioEngine.decodeSoundPlayer({data});
    });

    suite.afterEach(() => {
        soundPlayer.dispose();
        soundPlayer = null;
        audioEngine = null;
        audioContext.$reset();
        audioContext = null;
    });

    suite.plan(4);

    suite.test('play initializes and creates chain', t => {
        t.plan(3);
        t.equal(soundPlayer.initialized, false, 'not yet initialized');
        soundPlayer.play();
        t.equal(soundPlayer.initialized, true, 'now is initialized');
        let buffer = audioContext.DECODE_AUDIO_DATA_RESULT.toJSON();
        t.deepEqual(soundPlayer.outputNode.toJSON(), {
            buffer,
            inputs: [],
            loop: false,
            loopEnd: 0,
            loopStart: 0,
            name: 'AudioBufferSourceNode',
            playbackRate: {
                inputs: [],
                value: 1
            }
        });

        t.end();
    });

    suite.test('connect', t => {
        t.plan(1);
        soundPlayer.play();
        soundPlayer.connect(audioEngine);
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [
            soundPlayer.outputNode.toJSON()
        ], 'output node connects to input node');
        t.end();
    });

    suite.test('stop decay', t => {
        t.plan(7);
        soundPlayer.play();
        soundPlayer.connect(audioEngine);
        const outputNode = soundPlayer.outputNode;

        audioContext.$processTo(0);
        soundPlayer.stop();
        t.equal(soundPlayer.outputNode, null, 'nullify outputNode immediately (taken sound is stopping)');
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [{
            name: 'GainNode',
            gain: {
                value: 1,
                inputs: []
            },
            inputs: [outputNode.toJSON()]
        }], 'output node connects to gain node to input node');

        audioContext.$processTo(audioEngine.DECAY_TIME / 2);
        const engineInputs = audioEngine.inputNode.toJSON().inputs;
        t.notEqual(engineInputs[0].gain.value, 1, 'gain value should not be 1');
        t.notEqual(engineInputs[0].gain.value, 0, 'gain value should not be 0');
        t.equal(outputNode.$state, 'PLAYING');

        audioContext.$processTo(audioEngine.DECAY_TIME);
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [{
            name: 'GainNode',
            gain: {
                value: 0,
                inputs: []
            },
            inputs: [outputNode.toJSON()]
        }], 'output node connects to gain node to input node decayed');

        t.equal(outputNode.$state, 'FINISHED');

        t.end();
    });

    suite.test('play while playing', async t => {
        t.plan(15);
        const log = [];
        soundPlayer.play();
        soundPlayer.finished().then(() => log.push('play 1 finished'));
        soundPlayer.connect(audioEngine);

        await Promise.resolve();

        audioContext.$processTo(0.005);
        t.equal(soundPlayer.outputNode.$state, 'PLAYING');

        const oldPlayerNode = soundPlayer.outputNode;
        soundPlayer.play();
        soundPlayer.finished().then(() => log.push('play 2 finished'));

        // wait for a micro-task loop to fire our previous events
        await Promise.resolve();
        t.equal(log[0], 'play 1 finished');
        t.notEqual(soundPlayer.outputNode, oldPlayerNode, 'created new player node');

        t.equal(help.engineInputs.length, 2, 'there should be 2 players connected');
        t.equal(oldPlayerNode.$state, 'PLAYING');
        t.equal(soundPlayer.outputNode.$state, 'PLAYING');
        t.equal(help.engineInputs[0].gain.value, 1, 'old sound connectect to gain node with volume 1');

        audioContext.$processTo(audioContext.currentTime + 0.001);
        t.notEqual(help.engineInputs[0].gain.value, 1,
            'old sound connected to gain node which will fade');

        audioContext.$processTo(audioContext.currentTime + audioEngine.DECAY_TIME + 0.001);
        t.equal(soundPlayer.outputNode.$state, 'PLAYING');
        t.equal(oldPlayerNode.$state, 'FINISHED');

        t.equal(help.engineInputs[0].gain.value, 0, 'faded old sound to 0');

        t.equal(log.length, 1);
        audioContext.$processTo(audioContext.currentTime + 0.2);

        await Promise.resolve();
        t.equal(log[1], 'play 2 finished');
        t.equal(help.engineInputs.length, 1, 'old sound disconneted itself after done');
        t.equal(log.length, 2);

        t.end();
    });

    suite.end();
});
<commit_msg>convert async/await to promise chain<commit_after>/* global Uint8Array Promise */
const tap = require('tap');
const {AudioContext} = require('web-audio-test-api');

const AudioEngine = require('../src/AudioEngine');

tap.test('SoundPlayer', suite => {

    let audioContext;
    let audioEngine;
    let soundPlayer;

    const help = {
        get engineInputs () {
            return audioEngine.inputNode.toJSON().inputs;
        }
    };

    suite.beforeEach(async () => {
        audioContext = new AudioContext();
        audioEngine = new AudioEngine(audioContext);
        // sound will be 0.1 seconds long
        audioContext.DECODE_AUDIO_DATA_RESULT = audioContext.createBuffer(2, 4410, 44100);
        audioContext.DECODE_AUDIO_DATA_FAILED = false;
        const data = new Uint8Array(44100);
        soundPlayer = await audioEngine.decodeSoundPlayer({data});
    });

    suite.afterEach(() => {
        soundPlayer.dispose();
        soundPlayer = null;
        audioEngine = null;
        audioContext.$reset();
        audioContext = null;
    });

    suite.plan(4);

    suite.test('play initializes and creates chain', t => {
        t.plan(3);
        t.equal(soundPlayer.initialized, false, 'not yet initialized');
        soundPlayer.play();
        t.equal(soundPlayer.initialized, true, 'now is initialized');
        let buffer = audioContext.DECODE_AUDIO_DATA_RESULT.toJSON();
        t.deepEqual(soundPlayer.outputNode.toJSON(), {
            buffer,
            inputs: [],
            loop: false,
            loopEnd: 0,
            loopStart: 0,
            name: 'AudioBufferSourceNode',
            playbackRate: {
                inputs: [],
                value: 1
            }
        });

        t.end();
    });

    suite.test('connect', t => {
        t.plan(1);
        soundPlayer.play();
        soundPlayer.connect(audioEngine);
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [
            soundPlayer.outputNode.toJSON()
        ], 'output node connects to input node');
        t.end();
    });

    suite.test('stop decay', t => {
        t.plan(7);
        soundPlayer.play();
        soundPlayer.connect(audioEngine);
        const outputNode = soundPlayer.outputNode;

        audioContext.$processTo(0);
        soundPlayer.stop();
        t.equal(soundPlayer.outputNode, null, 'nullify outputNode immediately (taken sound is stopping)');
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [{
            name: 'GainNode',
            gain: {
                value: 1,
                inputs: []
            },
            inputs: [outputNode.toJSON()]
        }], 'output node connects to gain node to input node');

        audioContext.$processTo(audioEngine.DECAY_TIME / 2);
        const engineInputs = audioEngine.inputNode.toJSON().inputs;
        t.notEqual(engineInputs[0].gain.value, 1, 'gain value should not be 1');
        t.notEqual(engineInputs[0].gain.value, 0, 'gain value should not be 0');
        t.equal(outputNode.$state, 'PLAYING');

        audioContext.$processTo(audioEngine.DECAY_TIME);
        t.deepEqual(audioEngine.inputNode.toJSON().inputs, [{
            name: 'GainNode',
            gain: {
                value: 0,
                inputs: []
            },
            inputs: [outputNode.toJSON()]
        }], 'output node connects to gain node to input node decayed');

        t.equal(outputNode.$state, 'FINISHED');

        t.end();
    });

    suite.test('play while playing', t => {
        t.plan(15);
        const log = [];
        soundPlayer.play();
        soundPlayer.finished().then(() => log.push('play 1 finished'));
        soundPlayer.connect(audioEngine);
        let oldPlayerNode;

        return Promise.resolve()
            .then(() => {

                audioContext.$processTo(0.005);
                t.equal(soundPlayer.outputNode.$state, 'PLAYING');

                oldPlayerNode = soundPlayer.outputNode;
                soundPlayer.play();
                soundPlayer.finished().then(() => log.push('play 2 finished'));

            // wait for a micro-task loop to fire our previous events
                return Promise.resolve();
            })
            .then(() => {

                t.equal(log[0], 'play 1 finished');
                t.notEqual(soundPlayer.outputNode, oldPlayerNode, 'created new player node');

                t.equal(help.engineInputs.length, 2, 'there should be 2 players connected');
                t.equal(oldPlayerNode.$state, 'PLAYING');
                t.equal(soundPlayer.outputNode.$state, 'PLAYING');
                t.equal(help.engineInputs[0].gain.value, 1, 'old sound connectect to gain node with volume 1');

                audioContext.$processTo(audioContext.currentTime + 0.001);
                t.notEqual(help.engineInputs[0].gain.value, 1,
            'old sound connected to gain node which will fade');

                audioContext.$processTo(audioContext.currentTime + audioEngine.DECAY_TIME + 0.001);
                t.equal(soundPlayer.outputNode.$state, 'PLAYING');
                t.equal(oldPlayerNode.$state, 'FINISHED');

                t.equal(help.engineInputs[0].gain.value, 0, 'faded old sound to 0');

                t.equal(log.length, 1);
                audioContext.$processTo(audioContext.currentTime + 0.2);

            // wait for a micro-task loop to fire our previous events
                return Promise.resolve();
            })
            .then(() => {

                t.equal(log[1], 'play 2 finished');
                t.equal(help.engineInputs.length, 1, 'old sound disconneted itself after done');
                t.equal(log.length, 2);

                t.end();
            });
    });

    suite.end();
});
<|endoftext|>


<commit_before>module.exports = {
  "env": {
    "browser": true,
    "node": true,
    "amd": true
  },
  "extends": "eslint:recommended",
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "single"
    ],
    "semi": [
      "error",
      "always"
    ]
  }
};
<commit_msg>Tell ESLint that we use modules<commit_after>module.exports = {
  "env": {
    "browser": true,
    "node": true,
    "amd": true
  },
  "extends": "eslint:recommended",
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "single"
    ],
    "semi": [
      "error",
      "always"
    ]
  },
  "parserOptions": {
    "sourceType": "module"
  }
};
<|endoftext|>


<commit_before>/* global $:true */
/* global ymaps:true */

$('.map-group').each((idx, el) => {
    var id = $(el).find('.ymap').attr('id');
    var coords = $(el).find('.location').val();
    ymaps.ready(init.bind(this, id.slice(3, id.length), [coords.split(',')[0], coords.split(',')[1]]));
});

$('main').on('click', '.add-quest__button', function () {
    $('.add-quest__tabs').find('.tabs__link.active').removeClass('active');
    $('.quests-content').find('.tab-content__panel.active').removeClass('active');

    var id = generateId();

    var tab = createTab(id);
    $('.add-quest__tabs').append(tab);

    var tabContent = createTabContent(id);
    ymaps.ready(init.bind(this, id));
    $('.quests-content').append(tabContent);
});

function generateId() {
    var text = '';
    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    for (var i = 0; i < 7; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    return text;
}

function createTab(id) {
    return '
\n' +
        'Без названия\n' +
    '
';
}

function createTabContent(id) {
    return '
' +
        '
' +
        '
' +
        '
' +
        '<input name="image" type="file" accept="image/jpeg, image/png" class="add-quest__input" required>' +
        '
Перетащите сюда картинку или нажмите на кнопку
' +
    '
' +
        '<button type="button" class="btn add-quest__add-image">Добавить</button>' +
        '
' +
        '
' +
        '
' +
        '' +
        '' +
        '
' +
        '
' +
        '
' +
        '
' +
        'Название' +
        '<input name="title" type="text" class="input-group__input input_dark title" maxlength="30" required>' +
    '
' +
    '
' +
        'Месторасположение' +
        '<input type="text" class="location" id="coords' + $.parseHTML(id)[0].data + '" hidden required>' +
        '

' +
    '
' +
    '
' +
        'Описание' +
        '<textarea name="description" class="input-group__textarea description"></textarea>' +
        '
' +
        '
' +
        '
' +
        '<button type="button" class="btn btn_primary float_right add-quest_delete">Удалить</button>' +
        '
';
}

function init(id, position) {
    var yekatCoords = [56.837527, 60.605943];
    var map = new ymaps.Map('map' + id, {
        center: yekatCoords,
        zoom: 12
    });
    var placemark = new ymaps.Placemark(yekatCoords);
    getAddress(yekatCoords, placemark, id);
    map.controls.remove('geolocationControl');
    map.geoObjects.add(placemark);

    var searchControl = map.controls.get('searchControl');
    searchControl.events.add('resultselect', function (e) {
        var index = e.get('index');
        var target = e.get('target');
        target.getResult(index)
            .then(function (result) {
                var coords = result.geometry.getCoordinates();
                updatePlacemark(placemark, coords, id);
            });
        target.hideResult();
    });

    // Обработк кликов
    map.events.add('click', function (e) {
        var coords = e.get('coords');
        updatePlacemark(placemark, coords, id);
    });

    var geolocationControl = addGeolocation(placemark, id);
    map.controls.add(geolocationControl);

    if (position) {
        updatePlacemark(placemark, position, id);
    }
}

function getAddress(coords, placemark, id) {
    placemark.properties.set('iconCaption', 'поиск...');
    ymaps.geocode(coords).then(function (res) {
        var firstGeoObject = res.geoObjects.get(0);
        placemark.properties
            .set({
                iconCaption: [
                    firstGeoObject.getLocalities().length ? firstGeoObject.getLocalities() : firstGeoObject.getAdministrativeAreas(),
                    firstGeoObject.getThoroughfare() || firstGeoObject.getPremise()
                ].filter(Boolean).join(', '),
                balloonContent: firstGeoObject.getAddressLine()
            });
        document.getElementById('coords' + id).value = coords;
    });
}

function addGeolocation(placemark, id) {
    var geolocationControl = new ymaps.control.GeolocationControl({
        options: {noPlacemark: true}
    });
    geolocationControl.events.add('locationchange', function (event) {
        var position = event.get('position');
        updatePlacemark(placemark, position, id);
    });

    return geolocationControl;
}

function updatePlacemark(placemark, coords, id) {
    placemark.geometry.setCoordinates(coords);
    getAddress(coords, placemark, id);
}
<commit_msg>Эскейп (#275)<commit_after>/* global $:true */
/* global ymaps:true */

$('.map-group').each((idx, el) => {
    var id = $(el).find('.ymap').attr('id');
    var coords = $(el).find('.location').val();
    ymaps.ready(init.bind(this, id.slice(3, id.length), [coords.split(',')[0], coords.split(',')[1]]));
});

$('main').on('click', '.add-quest__button', function () {
    $('.add-quest__tabs').find('.tabs__link.active').removeClass('active');
    $('.quests-content').find('.tab-content__panel.active').removeClass('active');

    var id = generateId();

    var tab = createTab(id);
    $('.add-quest__tabs').append(tab);

    var tabContent = createTabContent(id);
    ymaps.ready(init.bind(this, id));
    $('.quests-content').append(tabContent);
});

function generateId() {
    var text = '';
    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    for (var i = 0; i < 7; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    return text;
}

function createTab(id) {
    return '
\n' +
        'Без названия\n' +
    '
';
}

function createTabContent(id) {
    var escaped = encodeURI(id);
    return '
' +
        '
' +
        '
' +
        '
' +
        '<input name="image" type="file" accept="image/jpeg, image/png" class="add-quest__input" required>' +
        '
Перетащите сюда картинку или нажмите на кнопку
' +
    '
' +
        '<button type="button" class="btn add-quest__add-image">Добавить</button>' +
        '
' +
        '
' +
        '
' +
        '' +
        '' +
        '
' +
        '
' +
        '
' +
        '
' +
        'Название' +
        '<input name="title" type="text" class="input-group__input input_dark title" maxlength="30" required>' +
    '
' +
    '
' +
        'Месторасположение' +
        '<input type="text" class="location" id="coords' + escaped + '" hidden required>' +
        '

' +
    '
' +
    '
' +
        'Описание' +
        '<textarea name="description" class="input-group__textarea description"></textarea>' +
        '
' +
        '
' +
        '
' +
        '<button type="button" class="btn btn_primary float_right add-quest_delete">Удалить</button>' +
        '
';
}

function init(id, position) {
    var yekatCoords = [56.837527, 60.605943];
    var map = new ymaps.Map('map' + id, {
        center: yekatCoords,
        zoom: 12
    });
    var placemark = new ymaps.Placemark(yekatCoords);
    getAddress(yekatCoords, placemark, id);
    map.controls.remove('geolocationControl');
    map.geoObjects.add(placemark);

    var searchControl = map.controls.get('searchControl');
    searchControl.events.add('resultselect', function (e) {
        var index = e.get('index');
        var target = e.get('target');
        target.getResult(index)
            .then(function (result) {
                var coords = result.geometry.getCoordinates();
                updatePlacemark(placemark, coords, id);
            });
        target.hideResult();
    });

    // Обработк кликов
    map.events.add('click', function (e) {
        var coords = e.get('coords');
        updatePlacemark(placemark, coords, id);
    });

    var geolocationControl = addGeolocation(placemark, id);
    map.controls.add(geolocationControl);

    if (position) {
        updatePlacemark(placemark, position, id);
    }
}

function getAddress(coords, placemark, id) {
    placemark.properties.set('iconCaption', 'поиск...');
    ymaps.geocode(coords).then(function (res) {
        var firstGeoObject = res.geoObjects.get(0);
        placemark.properties
            .set({
                iconCaption: [
                    firstGeoObject.getLocalities().length ? firstGeoObject.getLocalities() : firstGeoObject.getAdministrativeAreas(),
                    firstGeoObject.getThoroughfare() || firstGeoObject.getPremise()
                ].filter(Boolean).join(', '),
                balloonContent: firstGeoObject.getAddressLine()
            });
        document.getElementById('coords' + id).value = coords;
    });
}

function addGeolocation(placemark, id) {
    var geolocationControl = new ymaps.control.GeolocationControl({
        options: {noPlacemark: true}
    });
    geolocationControl.events.add('locationchange', function (event) {
        var position = event.get('position');
        updatePlacemark(placemark, position, id);
    });

    return geolocationControl;
}

function updatePlacemark(placemark, coords, id) {
    placemark.geometry.setCoordinates(coords);
    getAddress(coords, placemark, id);
}
<|endoftext|>


<commit_before>module.exports = {
  parser: "babel-eslint",
  root: true,
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  extends: "airbnb",
  env: {
    browser: true
  },
  rules: {
  },
  globals: {
   $: true
  }
};
<commit_msg>add globals to eslintrc<commit_after>module.exports = {
  parser: "babel-eslint",
  root: true,
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  extends: "airbnb",
  env: {
    browser: true
  },
  rules: {
  },
  globals: {
   $: true,
   d3: true,
  }
};
<|endoftext|>


<commit_before>module.exports = {
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true,
    },
    "globals": {
        "_gaq": false,
        "process": false,
        "ActiveXObject": false,
        "VERSION": false,
        // Test globals
        "after": false,
        "afterEach": false,
        "assert": false,
        "before": false,
        "beforeEach": false,
        "describe": false,
        "expect": false,
        "it": false,
        "sinon": false,
        "xit": false
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "indent": [
            "error",
            2
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "never"
        ],
        "no-var": "error"
    }
};
<commit_msg>eslint: add build globals to allow editor based checks<commit_after>module.exports = {
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true,
    },
    "globals": {
        "_gaq": false,
        "process": false,
        "ActiveXObject": false,
        "VERSION": false,
        // Build globals
        "__dirname": false,
        // Test globals
        "after": false,
        "afterEach": false,
        "assert": false,
        "before": false,
        "beforeEach": false,
        "describe": false,
        "expect": false,
        "it": false,
        "sinon": false,
        "xit": false
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "indent": [
            "error",
            2
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "never"
        ],
        "no-var": "error"
    }
};
<|endoftext|>


<commit_before><commit_msg>chore(gatsby): Prevent eslint from changing type string quotes into backticks (#20454)<commit_after><|endoftext|>


<commit_before>'use strict'

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    jest: true,
  },

  globals: {
    RUN_TEST_AGAINST_AWS: true,
    TEST_BASE_URL: true,
  },

  parserOptions: {
    ecmaVersion: 'latest',
  },

  rules: {
    // overwrite airbnb-base options

    // require file extensions
    'import/extensions': ['error', 'always', { ignorePackages: true }],

    'no-restricted-exports': 'off',
    // import buffer explicitly
    'no-restricted-globals': [
      'error',
      {
        name: 'Buffer',
        message: "Import 'Buffer' from 'buffer' module instead",
      },
    ],
    // we use underscores to indicate private fields in classes
    'no-underscore-dangle': 'off',
    // we turn this off here, for all commonjs modules (e.g. test fixtures etc.)
    strict: ['off'],

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'lines-between-class-members': 'off',
    'no-console': 'off',
    'no-restricted-syntax': 'off',
  },
}
<commit_msg>chore(eslint): turn off import/no-relative-packages<commit_after>'use strict'

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    jest: true,
  },

  globals: {
    RUN_TEST_AGAINST_AWS: true,
    TEST_BASE_URL: true,
  },

  parserOptions: {
    ecmaVersion: 'latest',
  },

  rules: {
    // overwrite airbnb-base options

    // require file extensions
    'import/extensions': ['error', 'always', { ignorePackages: true }],

    'import/no-relative-packages': 'off',

    'no-restricted-exports': 'off',
    // import buffer explicitly
    'no-restricted-globals': [
      'error',
      {
        name: 'Buffer',
        message: "Import 'Buffer' from 'buffer' module instead",
      },
    ],
    // we use underscores to indicate private fields in classes
    'no-underscore-dangle': 'off',
    // we turn this off here, for all commonjs modules (e.g. test fixtures etc.)
    strict: ['off'],

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'lines-between-class-members': 'off',
    'no-console': 'off',
    'no-restricted-syntax': 'off',
  },
}
<|endoftext|>


<commit_before>module.exports = {
    parser: '@typescript-eslint/parser',
    plugins: ['@typescript-eslint', 'local'],
    extends: ['plugin:@typescript-eslint/recommended'],
    rules: {
        'array-bracket-spacing': ['error', 'never'],
        'block-spacing': ['error', 'always'],
        'indent': ['error', 4, {
            'SwitchCase': 1,
        }],
        'object-curly-spacing': ['error', 'never'],
        'no-multi-spaces': 'error',
        'no-trailing-spaces': 'error',
        'no-whitespace-before-property': 'error',
        'semi': ['error', 'always'],
        'space-before-function-paren': ['error', {
            anonymous: 'always',
            named: 'never',
            asyncArrow: 'always',
        }],
        'space-in-parens': ['error', 'never'],
        'spaced-comment': ['error', 'always', {exceptions: ['-']}],
        '@typescript-eslint/camelcase': 'off',
        '@typescript-eslint/explicit-function-return-type': 'off',
        '@typescript-eslint/explicit-member-accessibility': 'off',
        '@typescript-eslint/func-call-spacing': ['error', 'never'],
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-object-literal-type-assertion': 'off',
        '@typescript-eslint/no-use-before-define': 'off',
    },
    overrides: [
        {
            files: ['tasks/**/*.js'],
            rules: {
                '@typescript-eslint/no-var-requires': 'off',
            },
        },
        {
            files: ['**/*.tsx'],
            rules: {
                'local/jsx-uses-m-pragma': 'error',
                'local/jsx-uses-vars': 'error',
            },
        },
    ],
};
<commit_msg>Added more ESLint rules<commit_after>module.exports = {
    parser: '@typescript-eslint/parser',
    plugins: ['@typescript-eslint', 'local'],
    extends: ['plugin:@typescript-eslint/recommended'],
    rules: {
        'array-bracket-spacing': ['error', 'never'],
        'block-spacing': ['error', 'always'],
        'indent': ['error', 4, {
            'SwitchCase': 1,
        }],
        'keyword-spacing': ['error', {
            after: true,
            before: true,
        }],
        'object-curly-spacing': ['error', 'never'],
        'no-multi-spaces': 'error',
        'no-trailing-spaces': 'error',
        'no-whitespace-before-property': 'error',
        'semi': ['error', 'always'],
        'space-before-function-paren': ['error', {
            anonymous: 'always',
            asyncArrow: 'always',
            named: 'never',
        }],
        'space-in-parens': ['error', 'never'],
        'spaced-comment': ['error', 'always', {exceptions: ['-']}],
        'quotes': ['error', 'single', {
            allowTemplateLiterals: true,
            avoidEscape: true,
        }],
        '@typescript-eslint/camelcase': 'off',
        '@typescript-eslint/explicit-function-return-type': 'off',
        '@typescript-eslint/explicit-member-accessibility': 'off',
        '@typescript-eslint/func-call-spacing': ['error', 'never'],
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-object-literal-type-assertion': 'off',
        '@typescript-eslint/no-use-before-define': 'off',
    },
    overrides: [
        {
            files: ['tasks/**/*.js'],
            rules: {
                '@typescript-eslint/no-var-requires': 'off',
            },
        },
        {
            files: ['**/*.tsx'],
            rules: {
                'local/jsx-uses-m-pragma': 'error',
                'local/jsx-uses-vars': 'error',
            },
        },
    ],
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import Title from '../Title';
import GuestMode, { GoBack } from '../GuestMode';
import { Form, Input, Button } from '../Form';
import { Success } from '../Alerts';

import api from '../../utils/api';

import './RequestResetPassword.css';

class RequestResetPassword extends Component {
  constructor(...props) {
    super(...props);

    this.state = {
      isValid: false,
      success: false,
    };
  }

  requestResetPassword(body) {
    api.post('/auth/reset', body).then(() => {
      this.setState({ success: true });
    });
  }

  render() {
    const { isValid, success } = this.state;

    return (
      <GuestMode className="RequestResetPassword">
        <Title name="Reset Password" />

        {success && <Success body="We have sent you an e-mail to reset your password." />}

        {!success && (
          <Form
            onValidationChange={(valid) => this.setState({ isValid: valid })}
            onSubmit={(body) => this.requestResetPassword(body)}
          >

To reset, please fill in your email:

            <Input name="email" rules={['required', 'email']} placeholder="Email" className="RequestResetPassword__Input" />

              <Button disabled={!isValid}>Reset password</Button>

          </Form>
        )}
        <GoBack />
      </GuestMode>
    );
  }
}

export default RequestResetPassword;
<commit_msg>add email icon to RequestResetPassword<commit_after>import React, { Component } from 'react';

import Title from '../Title';
import GuestMode, { GoBack } from '../GuestMode';
import { Form, Input, Button } from '../Form';
import { Success } from '../Alerts';

import api from '../../utils/api';

import './RequestResetPassword.css';

class RequestResetPassword extends Component {
  constructor(...props) {
    super(...props);

    this.state = {
      isValid: false,
      success: false,
    };
  }

  requestResetPassword(body) {
    api.post('/auth/reset', body).then(() => {
      this.setState({ success: true });
    });
  }

  render() {
    const { isValid, success } = this.state;

    return (
      <GuestMode className="RequestResetPassword">
        <Title name="Reset Password" />

        {success && <Success body="We have sent you an e-mail to reset your password." />}

        {!success && (
          <Form
            onValidationChange={(valid) => this.setState({ isValid: valid })}
            onSubmit={(body) => this.requestResetPassword(body)}
          >

To reset, please fill in your email:

            <Input name="email" rules={['required', 'email']} placeholder="Email" className="RequestResetPassword__Input" icon="email" />

              <Button disabled={!isValid}>Reset password</Button>

          </Form>
        )}
        <GoBack />
      </GuestMode>
    );
  }
}

export default RequestResetPassword;
<|endoftext|>


<commit_before>import { Page, orderPagesBooklet } from '../book';
import { Mode, Paper, Layout, Marks } from '../main/Constants';
import { c, createEl } from '../dom';

import errorView from './error';
import padPages from './padPages';
import { gridLayout, printLayout, flipLayout } from './Layouts';
import listenForPrint from './listenForPrint';

const modeClasses = {};
modeClasses[Mode.PREVIEW] = c('view-preview');
modeClasses[Mode.PRINT] = c('view-print');
modeClasses[Mode.FLIPBOOK] = c('view-flip');

class Viewer {
  constructor({ pageSetup, mode, layout, marks, ControlsComponent }) {
    this.book = null;
    this.pageSetup = pageSetup;

    this.progressBar = createEl('.progress-bar');
    this.content = createEl('zoom-content');
    this.scaler = createEl('zoom-scaler', [this.content]);
    this.element = createEl('root', [this.progressBar, this.scaler]);

    this.doubleSided = true;
    this.printArrange = layout;

    this.setMarks(marks);
    this.mode = mode;
    this.element.classList.add(c('view-preview'));
    this.currentLeaf = 0;

    listenForPrint(() => this.setPrint());
    this.listenForResize();

    this.setPrint = this.setPrint.bind(this);

    if (ControlsComponent) {
      this.controls = new ControlsComponent(
        { Mode, Paper, Layout, Marks }, // Available options
        { // Initial props
          paper: this.pageSetup.paper,
          layout: this.printArrange,
          mode: this.mode,
          marks,
        },
        { // Actions
          setMode: (newMode) => {
            if (newMode === this.mode) return;
            this.mode = newMode;
            this.render();
          },
          setPaper: this.setSheetSize.bind(this),
          setLayout: this.setPrintArrange.bind(this),
          setMarks: this.setMarks.bind(this),
          getPageSize: () => this.pageSetup.displaySize,
        }
      );
      this.element.appendChild(this.controls.element);
    }

    this.element.classList.add(c('in-progress'));

    document.body.appendChild(this.element);
  }

  listenForResize() {
    window.addEventListener('resize', () => {
      if (!this.throttleResize) {
        this.updateZoom();
        this.throttleResize = setTimeout(() => {
          this.throttleResize = null;
        }, 20);
      }
    });
  }

  setInProgress() {
    this.element.classList.add(c('in-progress'));
    if (this.controls) this.controls.setInProgress();
  }

  get isTwoUp() {
    return this.printArrange !== Layout.PAGES;
  }

  get isShowingCropMarks() {
    return this.element.classList.contains(c('show-crop'));
  }
  set isShowingCropMarks(newVal) {
    if (newVal) this.element.classList.add(c('show-crop'));
    else this.element.classList.remove(c('show-crop'));
  }

  get isShowingBleedMarks() {
    return this.element.classList.contains(c('show-bleed-marks'));
  }
  set isShowingBleedMarks(newVal) {
    if (newVal) this.element.classList.add(c('show-bleed-marks'));
    else this.element.classList.remove(c('show-bleed-marks'));
  }

  setSheetSize(newVal) {
    this.pageSetup.paper = newVal;
    this.pageSetup.updateStyleVars();

    if (this.mode !== Mode.PRINT) {
      this.setPrint();
    }
    this.updateZoom();
    setTimeout(() => { this.updateZoom(); }, 300);
  }

  setPrintArrange(newVal) {
    if (newVal === this.printArrange) return;
    this.printArrange = newVal;

    this.pageSetup.setPrintTwoUp(this.isTwoUp);
    this.pageSetup.updateStyleVars();

    if (this.mode === Mode.PRINT) {
      this.render();
    } else {
      this.setPrint();
    }
  }

  setMarks(newVal) {
    this.isShowingCropMarks = (newVal === Marks.CROP || newVal === Marks.BOTH);
    this.isShowingBleedMarks = (newVal === Marks.BLEED || newVal === Marks.BOTH);
  }

  displayError(title, text) {
    if (!this.element.parentNode) {
      document.body.appendChild(this.element);
    }
    if (!this.error) {
      this.error = errorView(title, text);
      this.element.appendChild(this.error);
    }
  }
  clear() {
    this.book = null;
    this.lastSpreadInProgress = null; // TODO: Make this clearer, after first render
    this.content.innerHTML = '';
  }
  cancel() {
    // TODO this doesn't work if the target is an existing node
    if (this.element.parentNode) {
      this.element.parentNode.removeChild(this.element);
    }
  }
  toggleBleed() {
    this.element.classList.add(c('show-bleed'));
  }
  toggleDouble() {
    this.doubleSided = !this.doubleSided;
    this.render();
  }
  setPrint() {
    if (this.mode === Mode.PRINT) return;
    this.mode = Mode.PRINT;
    this.render();
  }
  render(newBook) {
    if (newBook) this.book = newBook;
    if (!this.book) return;
    const { body } = document;
    if (!this.element.parentNode) {
      body.appendChild(this.element);
    }

    body.classList.add(c('viewing'));
    this.element.classList.remove(...Object.keys(modeClasses).map(k => modeClasses[k]));
    this.element.classList.add(modeClasses[this.mode]);

    const scrollMax = body.scrollHeight - body.offsetHeight;
    const scrollPct = body.scrollTop / scrollMax;

    if (this.controls) this.controls.setDone(this.book.pages.length);
    this.progressBar.style.transform = '';

    window.requestAnimationFrame(() => {
      const pages = this.book.pages.slice();

      let frag;
      if (this.mode === Mode.FLIPBOOK) frag = this.renderInteractive(pages);
      else if (this.mode === Mode.PRINT) frag = this.renderPrint(pages);
      else frag = this.renderGrid(pages);

      this.content.innerHTML = '';
      this.content.appendChild(frag);
      body.scrollTop = scrollMax * scrollPct;
      this.updateZoom();
    });
  }

  set progress(p) {
    window.requestAnimationFrame(() => {
      this.progressBar.style.transform = `scaleX(${p})`;
    });
  }

  renderProgress(book) {
    this.book = book;
    const needsZoomUpdate = !this.content.firstElementChild;

    this.progress = book.estimatedProgress;

    if (this.controls) {
      this.controls.updateProgress(book.pageCount, book.estimatedProgress);
    }

    const sideBySide =
      this.mode === Mode.PREVIEW
      || (this.mode === Mode.PRINT && this.printArrange !== Layout.PAGES);
    const limit = sideBySide ? 2 : 1;

    const makeSpread = function (...arg) {
      return createEl('.spread-wrapper.spread-size', [...arg]);
    };

    this.book.pages.forEach((page, i) => {
      // If hasn't been added, or not in spread yet
      if (!this.content.contains(page.element) || page.element.parentNode === this.content) {
        if (this.lastSpreadInProgress && this.lastSpreadInProgress.children.length < limit) {
          this.lastSpreadInProgress.appendChild(page.element);
        } else {
          if (i === 0 && sideBySide) {
            const spacer = new Page();
            spacer.element.style.visibility = 'hidden';
            this.lastSpreadInProgress = makeSpread(spacer.element, page.element);
          } else {
            this.lastSpreadInProgress = makeSpread(page.element);
          }
          this.content.appendChild(this.lastSpreadInProgress);
        }
      }
    });

    if (this.book.currentPage) {
      this.content.appendChild(this.book.currentPage.element);
    }

    if (needsZoomUpdate) this.updateZoom();
  }

  updateZoom() {
    if (!this.content.firstElementChild) return;

    const scrollPct = document.body.scrollTop / document.body.scrollHeight;
    const viewerRect = this.scaler.getBoundingClientRect();
    const contentW = this.content.getBoundingClientRect().width;
    const scale = Math.min(1, viewerRect.width / (contentW));

    this.scaler.style.transform = `scale(${scale})`;
    document.body.scrollTop = document.body.scrollHeight * scrollPct;
  }

  renderPrint(bookPages) {
    let pages = bookPages;
    this.element.classList.add(c('show-bleed'));
    const isBooklet = this.printArrange === Layout.BOOKLET;
    if (this.printArrange === Layout.SPREADS) {
      pages = padPages(pages, () => new Page());
    } else if (isBooklet) {
      pages = orderPagesBooklet(pages, () => new Page());
    }
    return printLayout(pages, this.isTwoUp, isBooklet);
  }

  renderGrid(bookPages) {
    let pages = bookPages;
    this.element.classList.remove(c('show-bleed'));
    if (this.doubleSided) pages = padPages(pages, () => new Page());
    return gridLayout(pages, this.doubleSided);
  }

  renderInteractive(bookPages) {
    this.element.classList.remove(c('show-bleed'));
    const pages = padPages(bookPages, () => new Page());
    return flipLayout(pages, this.doubleSided);
  }
}

export default Viewer;
<commit_msg>simplify viewer resize throttle<commit_after>import { Page, orderPagesBooklet } from '../book';
import { Mode, Paper, Layout, Marks } from '../main/Constants';
import { c, createEl } from '../dom';
import { oncePerFrameLimiter } from '../utils';

import errorView from './error';
import padPages from './padPages';
import { gridLayout, printLayout, flipLayout } from './Layouts';
import listenForPrint from './listenForPrint';

const modeClasses = {};
modeClasses[Mode.PREVIEW] = c('view-preview');
modeClasses[Mode.PRINT] = c('view-print');
modeClasses[Mode.FLIPBOOK] = c('view-flip');

const throttleProgress = oncePerFrameLimiter();

class Viewer {
  constructor({ pageSetup, mode, layout, marks, ControlsComponent }) {
    this.book = null;
    this.pageSetup = pageSetup;

    this.progressBar = createEl('.progress-bar');
    this.content = createEl('zoom-content');
    this.scaler = createEl('zoom-scaler', [this.content]);
    this.element = createEl('root', [this.progressBar, this.scaler]);

    this.doubleSided = true;
    this.printArrange = layout;

    this.setMarks(marks);
    this.mode = mode;
    this.element.classList.add(c('view-preview'));
    this.currentLeaf = 0;

    listenForPrint(() => this.setPrint());

    const throttleResize = oncePerFrameLimiter();
    window.addEventListener('resize', () => {
      throttleResize(() => this.updateZoom());
    });

    this.setPrint = this.setPrint.bind(this);

    if (ControlsComponent) {
      this.controls = new ControlsComponent(
        { Mode, Paper, Layout, Marks }, // Available options
        { // Initial props
          paper: this.pageSetup.paper,
          layout: this.printArrange,
          mode: this.mode,
          marks,
        },
        { // Actions
          setMode: (newMode) => {
            if (newMode === this.mode) return;
            this.mode = newMode;
            this.render();
          },
          setPaper: this.setSheetSize.bind(this),
          setLayout: this.setPrintArrange.bind(this),
          setMarks: this.setMarks.bind(this),
          getPageSize: () => this.pageSetup.displaySize,
        }
      );
      this.element.appendChild(this.controls.element);
    }

    this.element.classList.add(c('in-progress'));

    document.body.appendChild(this.element);
  }

  setInProgress() {
    this.element.classList.add(c('in-progress'));
    if (this.controls) this.controls.setInProgress();
  }

  get isTwoUp() {
    return this.printArrange !== Layout.PAGES;
  }

  get isShowingCropMarks() {
    return this.element.classList.contains(c('show-crop'));
  }
  set isShowingCropMarks(newVal) {
    if (newVal) this.element.classList.add(c('show-crop'));
    else this.element.classList.remove(c('show-crop'));
  }

  get isShowingBleedMarks() {
    return this.element.classList.contains(c('show-bleed-marks'));
  }
  set isShowingBleedMarks(newVal) {
    if (newVal) this.element.classList.add(c('show-bleed-marks'));
    else this.element.classList.remove(c('show-bleed-marks'));
  }

  setSheetSize(newVal) {
    this.pageSetup.paper = newVal;
    this.pageSetup.updateStyleVars();

    if (this.mode !== Mode.PRINT) {
      this.setPrint();
    }
    this.updateZoom();
    setTimeout(() => { this.updateZoom(); }, 300);
  }

  setPrintArrange(newVal) {
    if (newVal === this.printArrange) return;
    this.printArrange = newVal;

    this.pageSetup.setPrintTwoUp(this.isTwoUp);
    this.pageSetup.updateStyleVars();

    if (this.mode === Mode.PRINT) {
      this.render();
    } else {
      this.setPrint();
    }
  }

  setMarks(newVal) {
    this.isShowingCropMarks = (newVal === Marks.CROP || newVal === Marks.BOTH);
    this.isShowingBleedMarks = (newVal === Marks.BLEED || newVal === Marks.BOTH);
  }

  displayError(title, text) {
    if (!this.element.parentNode) {
      document.body.appendChild(this.element);
    }
    if (!this.error) {
      this.error = errorView(title, text);
      this.element.appendChild(this.error);
    }
  }
  clear() {
    this.book = null;
    this.lastSpreadInProgress = null; // TODO: Make this clearer, after first render
    this.content.innerHTML = '';
  }
  cancel() {
    // TODO this doesn't work if the target is an existing node
    if (this.element.parentNode) {
      this.element.parentNode.removeChild(this.element);
    }
  }
  toggleBleed() {
    this.element.classList.add(c('show-bleed'));
  }
  toggleDouble() {
    this.doubleSided = !this.doubleSided;
    this.render();
  }
  setPrint() {
    if (this.mode === Mode.PRINT) return;
    this.mode = Mode.PRINT;
    this.render();
  }
  render(newBook) {
    if (newBook) this.book = newBook;
    if (!this.book) return;
    const { body } = document;
    if (!this.element.parentNode) {
      body.appendChild(this.element);
    }

    body.classList.add(c('viewing'));
    this.element.classList.remove(...Object.keys(modeClasses).map(k => modeClasses[k]));
    this.element.classList.add(modeClasses[this.mode]);

    const scrollMax = body.scrollHeight - body.offsetHeight;
    const scrollPct = body.scrollTop / scrollMax;

    if (this.controls) this.controls.setDone(this.book.pages.length);
    this.progressBar.style.transform = '';

    window.requestAnimationFrame(() => {
      const pages = this.book.pages.slice();

      let frag;
      if (this.mode === Mode.FLIPBOOK) frag = this.renderInteractive(pages);
      else if (this.mode === Mode.PRINT) frag = this.renderPrint(pages);
      else frag = this.renderGrid(pages);

      this.content.innerHTML = '';
      this.content.appendChild(frag);
      body.scrollTop = scrollMax * scrollPct;
      this.updateZoom();
    });
  }

  set progress(p) {
    throttleProgress(() => {
      this.progressBar.style.transform = `scaleX(${p})`;
    });
  }

  renderProgress(book) {
    this.book = book;
    const needsZoomUpdate = !this.content.firstElementChild;

    this.progress = book.estimatedProgress;

    if (this.controls) {
      this.controls.updateProgress(book.pageCount, book.estimatedProgress);
    }

    const sideBySide =
      this.mode === Mode.PREVIEW
      || (this.mode === Mode.PRINT && this.printArrange !== Layout.PAGES);
    const limit = sideBySide ? 2 : 1;

    const makeSpread = function (...arg) {
      return createEl('.spread-wrapper.spread-size', [...arg]);
    };

    this.book.pages.forEach((page, i) => {
      // If hasn't been added, or not in spread yet
      if (!this.content.contains(page.element) || page.element.parentNode === this.content) {
        if (this.lastSpreadInProgress && this.lastSpreadInProgress.children.length < limit) {
          this.lastSpreadInProgress.appendChild(page.element);
        } else {
          if (i === 0 && sideBySide) {
            const spacer = new Page();
            spacer.element.style.visibility = 'hidden';
            this.lastSpreadInProgress = makeSpread(spacer.element, page.element);
          } else {
            this.lastSpreadInProgress = makeSpread(page.element);
          }
          this.content.appendChild(this.lastSpreadInProgress);
        }
      }
    });

    if (this.book.currentPage) {
      this.content.appendChild(this.book.currentPage.element);
    }

    if (needsZoomUpdate) this.updateZoom();
  }

  updateZoom() {
    if (!this.content.firstElementChild) return;

    const scrollPct = document.body.scrollTop / document.body.scrollHeight;
    const viewerRect = this.scaler.getBoundingClientRect();
    const contentW = this.content.getBoundingClientRect().width;
    const scale = Math.min(1, viewerRect.width / (contentW));

    this.scaler.style.transform = `scale(${scale})`;
    document.body.scrollTop = document.body.scrollHeight * scrollPct;
  }

  renderPrint(bookPages) {
    let pages = bookPages;
    this.element.classList.add(c('show-bleed'));
    const isBooklet = this.printArrange === Layout.BOOKLET;
    if (this.printArrange === Layout.SPREADS) {
      pages = padPages(pages, () => new Page());
    } else if (isBooklet) {
      pages = orderPagesBooklet(pages, () => new Page());
    }
    return printLayout(pages, this.isTwoUp, isBooklet);
  }

  renderGrid(bookPages) {
    let pages = bookPages;
    this.element.classList.remove(c('show-bleed'));
    if (this.doubleSided) pages = padPages(pages, () => new Page());
    return gridLayout(pages, this.doubleSided);
  }

  renderInteractive(bookPages) {
    this.element.classList.remove(c('show-bleed'));
    const pages = padPages(bookPages, () => new Page());
    return flipLayout(pages, this.doubleSided);
  }
}

export default Viewer;
<|endoftext|>


<commit_before>// run this test from root path
// because paths are made that way

var errors = [];

var loadModule = function(path) {
	try {
		return require(path);
	}
	catch(e) {
		errors.push(e.message);
	}
};

var loadFile = function(fpath) {
	try {
		return fs.readFileSync(path.resolve(__dirname, fpath));
	}
	catch(e) {
		if(e.code === "ENOENT") {
			errors.push("Could not load the file, make sure it exists: " + e.path);
		} else {
			errors.push("There was some error. Please report it if you can: " + e.message);
		}
		return null;
	}
};

var printErrors = function() {
	if(errors.length > 0) {
		console.log("+++ There were some errors in test setup. Probably missing dependecies or files. If you think it is a bug, send me an email or report an issue ;-)");
		console.log("INFO: Current working directory: " + process.cwd());
		console.log("INFO: Current path: " + __dirname);
		console.log("INFO: Current script: " + __filename);
		errors.forEach(function(element, index, array) {
			console.log("-", element);
		});
		return 1;
	}
};

var validateSchema = function(schema, sample) {
	// TODO handle external schemas
	var externalSchemas = {};
	var val = validator(schema);
	// greedy tries to validate the most of the document
	var vali = val(sample, {schemas: externalSchemas, greedy: true});
  return vali;
};

global.fs = loadModule("fs");
global.chai = loadModule("chai");
global.assert = chai.assert;
global.glob = loadModule("glob");
global.validator = loadModule("is-my-json-valid");
global.path = loadModule("path");

global.schema = loadFile('../src/schemaschema-draft04/schema.json');
global.hyperschema = loadFile('../src/schemaschema-draft04/hyper-schema.json');

printErrors();

// globs needs to be synchronous for tests to work properly!
console.log('Common errors:');
console.log('SyntaxError: Unexpected token } - malformated JSON');
console.log('SyntaxError: Unexpected end of input - JSON file is empty, must be at least {} or []');

describe('All JSON Schemas', function() {
  var schemaFiles = glob.sync(path.resolve(__dirname, "../src/**/*.js[on]?"), null);

	if(schemaFiles.length === 0) {
		console.log("No JSON schemas found in src/, maybe download some from schemas.myrtana.sk or create a new one?");
	}
	else {
		schemaFiles.forEach(function(schemaFile, index, arr) {
			// load given JSON schema
			var schemaURI = path.relative(path.join(__dirname, "..", "src"), schemaFile);
			console.log("Found schema:", schemaURI);

			it(schemaFile +' is a valid JSON', function(){
				assert.doesNotThrow(
					function() { JSON.parse(fs.readFileSync(schemaFile));	},
					SyntaxError
				);
			});

			// while JSON validity is tested above, it is async
			// and must load and catch by hand
			var schemaContent = null;

			try {
				schemaContent = JSON.parse(fs.readFileSync(schemaFile));
			} catch(err) {}

			// test samples only if source schema is valid
			if(schemaContent) {
				var invalidSamplesFiles = path.resolve(__dirname, path.join('samples', schemaURI, "*invalid*.json"));
				var validSamplesFiles = path.resolve(__dirname, path.join('samples', schemaURI, "!(*invalid*).json"));

				// load respective tests for a schema
				// fail test if no valid or invalid tests are found
				describe("Schema '" + schemaURI + "'", function() {
					describe("Invalid samples", function() {
						var invalidSamples = glob.sync(invalidSamplesFiles, null);

					  it('has some invalid examples', function() {
							assert.isAbove(invalidSamples.length, 0, 'no invalid samples found');
						});

						invalidSamples.forEach(function(sampleURI, index, arr) {
							it(sampleURI, function() {
								assert.doesNotThrow(
									function() { JSON.parse(fs.readFileSync(sampleURI));	},
									SyntaxError
								);

								var sample = JSON.parse(fs.readFileSync(sampleURI));
								assert.equal(validateSchema(schemaContent, sample), false, 'IS VALID');
							});
						});
					});

					describe("Valid samples", function() {
						var validSamples = glob.sync(validSamplesFiles, null);

					  it('has some valid examples', function() {
							assert.isAbove(validSamples.length, 0, 'no valid samples found');
						});

						validSamples.forEach(function(sampleURI, index, arr) {
							it(sampleURI, function() {
								assert.doesNotThrow(
									function() { JSON.parse(fs.readFileSync(sampleURI));	},
									SyntaxError
								);

								var sample = JSON.parse(fs.readFileSync(sampleURI));
								var validated = validateSchema(schemaContent, sample);
								assert.equal(validated, true, 'IS NOT VALID: ' + validated.errors);
							});
						});
					});

				}); // end desribe schema
			} // end if schemaContent

		});
	}
});

<commit_msg>show validation errors<commit_after>// run this test from root path
// because paths are made that way

var errors = [];

var loadModule = function(path) {
	try {
		return require(path);
	}
	catch(e) {
		errors.push(e.message);
	}
};

var loadFile = function(fpath) {
	try {
		return fs.readFileSync(path.resolve(__dirname, fpath));
	}
	catch(e) {
		if(e.code === "ENOENT") {
			errors.push("Could not load the file, make sure it exists: " + e.path);
		} else {
			errors.push("There was some error. Please report it if you can: " + e.message);
		}
		return null;
	}
};

var printErrors = function() {
	if(errors.length > 0) {
		console.log("+++ There were some errors in test setup. Probably missing dependecies or files. If you think it is a bug, send me an email or report an issue ;-)");
		console.log("INFO: Current working directory: " + process.cwd());
		console.log("INFO: Current path: " + __dirname);
		console.log("INFO: Current script: " + __filename);
		errors.forEach(function(element, index, array) {
			console.log("-", element);
		});
		return 1;
	}
};

var jsonErrors = null;

var validateSchema = function(schema, sample) {
	// TODO handle external schemas
	var externalSchemas = {};
	var val = validator(schema);
	// greedy tries to validate the most of the document
	var vali = val(sample, {schemas: externalSchemas, greedy: true});
	jsonErrors = val.errors;
  return vali;
};

global.fs = loadModule("fs");
global.chai = loadModule("chai");
global.assert = chai.assert;
global.glob = loadModule("glob");
global.validator = loadModule("is-my-json-valid");
global.path = loadModule("path");

global.schema = loadFile('../src/schemaschema-draft04/schema.json');
global.hyperschema = loadFile('../src/schemaschema-draft04/hyper-schema.json');

printErrors();

// globs needs to be synchronous for tests to work properly!
console.log('Common errors:');
console.log('SyntaxError: Unexpected token } - malformated JSON');
console.log('SyntaxError: Unexpected end of input - JSON file is empty, must be at least {} or []');

describe('All JSON Schemas', function() {
  var schemaFiles = glob.sync(path.resolve(__dirname, "../src/**/*.js[on]?"), null);

	if(schemaFiles.length === 0) {
		console.log("No JSON schemas found in src/, maybe download some from schemas.myrtana.sk or create a new one?");
	}
	else {
		schemaFiles.forEach(function(schemaFile, index, arr) {
			// load given JSON schema
			var schemaURI = path.relative(path.join(__dirname, "..", "src"), schemaFile);
			console.log("Found schema:", schemaURI);

			it(schemaFile +' is a valid JSON', function(){
				assert.doesNotThrow(
					function() { JSON.parse(fs.readFileSync(schemaFile));	},
					SyntaxError
				);
			});

			// while JSON validity is tested above, it is async
			// and must load and catch by hand
			var schemaContent = null;

			try {
				schemaContent = JSON.parse(fs.readFileSync(schemaFile));
			} catch(err) {}

			// test samples only if source schema is valid
			if(schemaContent) {
				var invalidSamplesFiles = path.resolve(__dirname, path.join('samples', schemaURI, "*invalid*.json"));
				var validSamplesFiles = path.resolve(__dirname, path.join('samples', schemaURI, "!(*invalid*).json"));

				// load respective tests for a schema
				// fail test if no valid or invalid tests are found
				describe("Schema '" + schemaURI + "'", function() {
					describe("Invalid samples", function() {
						var invalidSamples = glob.sync(invalidSamplesFiles, null);

					  it('has some invalid examples', function() {
							assert.isAbove(invalidSamples.length, 0, 'no invalid samples found');
						});

						invalidSamples.forEach(function(sampleURI, index, arr) {
							it(sampleURI, function() {
								assert.doesNotThrow(
									function() { JSON.parse(fs.readFileSync(sampleURI));	},
									SyntaxError
								);

								var sample = JSON.parse(fs.readFileSync(sampleURI));
								assert.equal(validateSchema(schemaContent, sample), false, 'IS VALID');
							});
						});
					});

					describe("Valid samples", function() {
						var validSamples = glob.sync(validSamplesFiles, null);

					  it('has some valid examples', function() {
							assert.isAbove(validSamples.length, 0, 'no valid samples found');
						});

						validSamples.forEach(function(sampleURI, index, arr) {
							it(sampleURI, function() {
								assert.doesNotThrow(
									function() { JSON.parse(fs.readFileSync(sampleURI));	},
									SyntaxError
								);

								var sample = JSON.parse(fs.readFileSync(sampleURI));
								var validated = validateSchema(schemaContent, sample);
								console.log(validated);
								assert.equal(validated, true, 'IS NOT VALID, cause: ' + JSON.stringify(jsonErrors));
							});
						});
					});

				}); // end desribe schema
			} // end if schemaContent

		});
	}
});

<|endoftext|>


<commit_before>const expect = require('chai').expect;
const mongoose = require('mongoose');
import fetch from 'node-fetch';
const request = require('superagent');
const dataMakers = require('../helpers/data');
const Game = require('../../app/models/game');

describe('api/release', () => {
  describe('GET', () => {
    it('should return the latest prod release for a game if no token is provided.', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      let gameSlug = gameResponse.body.data[0].slug;
      let releaseResponse = await request.get(
        `http://localhost:3000/api/release/${gameSlug}`
      );
      expect(releaseResponse.body.success).to.equal(true);
      expect(releaseResponse.body.data.game.slug).to.equal(gameSlug);
    });

    it('should return the dev releases for a game if a valid token is provided.', async function() {
      // make a write level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);

      await dataMakers.makeGame('dev');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // add the editor token's group to the game's definition
      let gameObject = await Game.getBySlug(gameSlug);
      gameObject.groups.push({
        group: editor.groups[0],
        permission: 2
      });
      await gameObject.save();

      // send the request
      let releaseParams = {
        token: token,
        status: 'dev'
      };

      let releaseResponse = await request
        .get(`http://localhost:3000/api/release/${gameSlug}`)
        .query(`token=${token}`)
        .query('status=dev');

      expect(releaseResponse.body.success).to.equal(true);
      expect(releaseResponse.body.data.game.slug).to.equal(gameSlug);
    });

    it('should provide caching headers for non dev games', async function() {
      const game = await dataMakers.makeGame('prod');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}`);
      expect(response.headers.get('Cache-Control')).to.not.equal(null);
    });

    it('should not provide caching headers for dev games', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}?status=dev`);
      expect(response.headers.get('Cache-Control')).to.equal(null);
    });

    it('should respond with a 404 if a request is made for a slug that does not exist', async function() {
      const response = await fetch(`http://localhost:3000/api/release/dont-exists`);
      expect(response.status).to.equal(404);
    });

    it('should respond with a 404 if a request is made for a game that does not have a prod release', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}`);
      expect(response.status).to.equal(404);
    });

    it('should respond with a 403 if a request is made for a dev release without a token', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}?status=dev`);
      const json = await response.json();

      expect(response.status).to.equal(403);
    });

    it('should respond with a 422 if the request had invalid fields', async function() {
      const response = await fetch('http://localhost:3000/api/release/doesntmatter?status=NOPE&token=tooshort');
      expect(response.status).to.equal(422);
    });
  });

  describe('POST', () => {
    it('should be able to make a new release on POST if has editor+ credentials for the game in question', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');

      // make a write level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // add the editor token's group to the game's definition
      let gameObject = await Game.getBySlug(gameSlug);
      gameObject.groups.push({
        group: editor.groups[0],
        permission: 2
      });
      await gameObject.save();

      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);
      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0',
        token: token
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(true);
    });

    it('should not be able to make a new release on POST if no token is provided.', async function() {
      // need to uhhh make some games.
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;
      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);
      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0'
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(false);
    });

    it('should still allow an admin user to create a game, even if they do not have privileges on that game', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');

      // make a admin level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);

      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0',
        token: token
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(true);
    });
  });
});
<commit_msg>add unauthorized token game permission test<commit_after>const expect = require('chai').expect;
const mongoose = require('mongoose');
import fetch from 'node-fetch';
const request = require('superagent');
const dataMakers = require('../helpers/data');
const Game = require('../../app/models/game');

describe('api/release', () => {
  describe('GET', () => {
    it('should return the latest prod release for a game if no token is provided.', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      let gameSlug = gameResponse.body.data[0].slug;
      let releaseResponse = await request.get(
        `http://localhost:3000/api/release/${gameSlug}`
      );
      expect(releaseResponse.body.success).to.equal(true);
      expect(releaseResponse.body.data.game.slug).to.equal(gameSlug);
    });

    it('should return the dev releases for a game if a valid token is provided.', async function() {
      // make a write level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);

      await dataMakers.makeGame('dev');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // add the editor token's group to the game's definition
      let gameObject = await Game.getBySlug(gameSlug);
      gameObject.groups.push({
        group: editor.groups[0],
        permission: 2
      });
      await gameObject.save();

      // send the request
      let releaseParams = {
        token: token,
        status: 'dev'
      };

      let releaseResponse = await request
        .get(`http://localhost:3000/api/release/${gameSlug}`)
        .query(`token=${token}`)
        .query('status=dev');

      expect(releaseResponse.body.success).to.equal(true);
      expect(releaseResponse.body.data.game.slug).to.equal(gameSlug);
    });

    it('should provide caching headers for non dev games', async function() {
      const game = await dataMakers.makeGame('prod');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}`);
      expect(response.headers.get('Cache-Control')).to.not.equal(null);
    });

    it('should not provide caching headers for dev games', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}?status=dev`);
      expect(response.headers.get('Cache-Control')).to.equal(null);
    });

    it('should respond with a 404 if a request is made for a slug that does not exist', async function() {
      const response = await fetch(`http://localhost:3000/api/release/dont-exists`);
      expect(response.status).to.equal(404);
    });

    it('should respond with a 404 if a request is made for a game that does not have a prod release', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}`);
      expect(response.status).to.equal(404);
    });

    it('should respond with a 403 if a request is made for a dev release without a token', async function() {
      const game = await dataMakers.makeGame('dev');
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}?status=dev`);
      const json = await response.json();

      expect(response.status).to.equal(403);
    });

    it('should respond with a 422 if the request had invalid fields', async function() {
      const response = await fetch('http://localhost:3000/api/release/doesntmatter?status=NOPE&token=tooshort');
      expect(response.status).to.equal(422);
    });

    const getMethods = (obj) => {
      let properties = new Set()
      let currentObj = obj
      do {
        Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
      } while ((currentObj = Object.getPrototypeOf(currentObj)))
      return [...properties.keys()].filter(item => typeof obj[item] === 'function')
    }

    it('should respond with a 403 when user does not have permissions to view game', async function() {
      // create test objects
      const game = await dataMakers.makeGame('dev');
      const release = await dataMakers.makeRelease(game._id, 'dev');
      const user = await dataMakers.makeUser(0);
      const token = await dataMakers.getUserToken(user);
      // query api
      const response = await fetch(`http://localhost:3000/api/release/${game.slug}?commitId=${release.commitId}&token=${token}&debug=true`);
      const body = JSON.parse(await response.text());

      // test status
      expect(response.status).to.equal(403);
      // test error message
      expect(body.error.toLowerCase()).to.equal('unauthorized token');
    });
  });

  describe('POST', () => {
    it('should be able to make a new release on POST if has editor+ credentials for the game in question', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');

      // make a write level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // add the editor token's group to the game's definition
      let gameObject = await Game.getBySlug(gameSlug);
      gameObject.groups.push({
        group: editor.groups[0],
        permission: 2
      });
      await gameObject.save();

      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);
      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0',
        token: token
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(true);
    });

    it('should not be able to make a new release on POST if no token is provided.', async function() {
      // need to uhhh make some games.
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;
      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);
      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0'
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(false);
    });

    it('should still allow an admin user to create a game, even if they do not have privileges on that game', async function() {
      await dataMakers.makeGame('prod');
      let gameResponse = await request.get('http://localhost:3000/api/games');

      // make a admin level access token
      let editor = await dataMakers.makeUser(2);
      let token = await dataMakers.getUserToken(editor);
      // get the game slug from the api response
      let gameSlug = gameResponse.body.data[0].slug;

      // make a new commit id for the new release
      let commitId = dataMakers.makeRandomString(40);

      // send the request
      let releaseParams = {
        status: 'dev',
        commitId: commitId,
        version: '1.0.0',
        token: token
      };

      let postResponse = await request
        .post(`http://localhost:3000/api/release/${gameSlug}`)
        .send(releaseParams);
      expect(postResponse.body.success).to.equal(true);
    });
  });
});
<|endoftext|>


<commit_before>var ObjectServer = require('../lib/ObjectServer')
var o  = require('atom').o(module)
var __ = require('fiber').__(module, true)

__(function() {
  module.exports = o({
    _type: ObjectServer
  })
})
<commit_msg>remove app-harness<commit_after><|endoftext|>