<commit_before>(function(global) {

  /**
   * Class: RemoteStorage.IndexedDB
   *
   *
   * IndexedDB Interface
   * -------------------
   *
   * This file exposes a get/put/delete interface, accessing data in an indexedDB.
   *
   * There are multiple parts to this interface:
   *
   *   - The RemoteStorage integration:
   *     - RemoteStorage.IndexedDB._rs_supported() determines if indexedDB support
   *       is available. If it isn't, RemoteStorage won't initialize the feature.
   *     - RemoteStorage.IndexedDB._rs_init() initializes the feature. It returns
   *       a promise that is fulfilled as soon as the database has been opened and
   *       migrated.
   *
   *   - The storage interface (RemoteStorage.IndexedDB object):
   *     - Usually this is accessible via "remoteStorage.local"
   *     - #get() takes a path and returns a promise.
   *     - #put() takes a path, body and contentType and also returns a promise.
   *       In addition it also takes a 'incoming' flag, which indicates that the
   *       change is not fresh, but synchronized from remote.
   *     - #delete() takes a path and also returns a promise. It also supports
   *       the 'incoming' flag described for #put().
   *     - #on('change', ...) events, being fired whenever something changes in
   *       the storage. Change events roughly follow the StorageEvent pattern.
   *       They have "oldValue" and "newValue" properties, which can be used to
   *       distinguish create/update/delete operations and analyze changes in
   *       change handlers. In addition they carry a "origin" property, which
   *       is either "window" or "remote". "remote" events are fired whenever the
   *       "incoming" flag is passed to #put() or #delete(). This is usually done
   *       by RemoteStorage.Sync.
   *
   *   - The revision interface (also on RemoteStorage.IndexedDB object):
   *     - #setRevision(path, revision) sets the current revision for the given
   *       path. Revisions are only generated by the remotestorage server, so
   *       this is usually done from RemoteStorage.Sync once a pending change
   *       has been pushed out.
   *     - #setRevisions(revisions) takes path/revision pairs in the form:
   *       [[path1, rev1], [path2, rev2], ...] and updates all revisions in a
   *       single transaction.
   *     - #getRevision(path) returns the currently stored revision for the given
   *       path.
   *
   *   - The changes interface (also on RemoteStorage.IndexedDB object):
   *     - Used to record local changes between sync cycles.
   *     - Changes are stored in a separate ObjectStore called "changes".
   *     - #_recordChange() records a change and is called by #put() and #delete(),
   *       given the "incoming" flag evaluates to false. It is private andshould
   *       never be used from the outside.
   *     - #changesBelow() takes a path and returns a promise that will be fulfilled
   *       with an Array of changes that are pending for the given path or below.
   *       This is usually done in a sync cycle to push out pending changes.
   *     - #clearChange removes the change for a given path. This is usually done
   *       RemoteStorage.Sync once a change has successfully been pushed out.
   *     - #setConflict sets conflict attributes on a change. It also fires the
   *       "conflict" event.
   *     - #on('conflict', ...) event. Conflict events usually have the following
   *       attributes: path, localAction and remoteAction. Both actions are either
   *       "PUT" or "DELETE". They also bring a "resolve" method, which can be
   *       called with either of the strings "remote" and "local" to mark the
   *       conflict as resolved. The actual resolution will usually take place in
   *       the next sync cycle.
   */

  var RS = RemoteStorage;

  var DEFAULT_DB_NAME = 'remotestorage';
  var DEFAULT_DB;

  function keepDirNode(node) {
    return Object.keys(node.body).length > 0 ||
      Object.keys(node.cached).length > 0;
  }

  function removeFromParent(nodes, path, key) {
    var parts = path.match(/^(.*\/)([^\/]+\/?)$/);
    if(parts) {
      var dirname = parts[1], basename = parts[2];
      nodes.get(dirname).onsuccess = function(evt) {
        var node = evt.target.result;
        delete node[key][basename];
        if(keepDirNode(node)) {
          nodes.put(node);
        } else {
          nodes.delete(node.path).onsuccess = function() {
            if(dirname != '/') {
              removeFromParent(nodes, dirname, key);
            }
          };
        }
      };
    }
  }

  function makeNode(path) {
    var node = { path: path };
    if(path[path.length - 1] == '/') {
      node.body = {};
      node.cached = {};
      node.contentType = 'application/json';
    }
    return node;
  }

  function addToParent(nodes, path, key) {
    var parts = path.match(/^(.*\/)([^\/]+\/?)$/);
    if(parts) {
      var dirname = parts[1], basename = parts[2];
      nodes.get(dirname).onsuccess = function(evt) {
        var node = evt.target.result || makeNode(dirname);
        node[key][basename] = true;
        nodes.put(node).onsuccess = function() {
          if(dirname != '/') {
            addToParent(nodes, dirname, key);
          }
        };
      };
    }
  }

  RS.IndexedDB = function(database) {
    this.db = database || DEFAULT_DB;
    RS.eventHandling(this, 'change', 'conflict');
  };
  RS.IndexedDB.prototype = {

    get: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var nodes = transaction.objectStore('nodes');
      var nodeReq = nodes.get(path);
      var node;
      nodeReq.onsuccess = function() {
        node = nodeReq.result;
      };
      transaction.oncomplete = function() {
        if(node) {
          promise.fulfill(200, node.body, node.contentType, node.revision);
        } else {
          promise.fulfill(404);
        }
      };
      return promise;
    },

    put: function(path, body, contentType, incoming) {
      var promise = promising();
      if(path[path.length - 1] == '/') { throw "Bad: don't PUT folders"; }
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      var nodes = transaction.objectStore('nodes');
      var oldNode;
      nodes.get(path).onsuccess = function(evt) {
        oldNode = evt.target.result;
        var node = {
          path: path, contentType: contentType, body: body
        };
        nodes.put(node).onsuccess = function() { addToParent(nodes, path, 'body'); };
      };
      transaction.oncomplete = function() {
        this._emit('change', {
          path: path,
          origin: incoming ? 'remote' : 'window',
          oldValue: oldNode ? oldNode.body : undefined,
          newValue: body
        });
        if(! incoming) {
          this._recordChange(path, { action: 'PUT' });
        }
        promise.fulfill(200);
      }.bind(this);
      return promise;
    },

    delete: function(path, incoming) {
      var promise = promising();
      if(path[path.length - 1] == '/') { throw "Bad: don't DELETE folders"; }
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      var nodes = transaction.objectStore('nodes');
      var oldNode;
      nodes.get(path).onsuccess = function(evt) {
        oldNode = evt.target.result;
        nodes.delete(path).onsuccess = function() {
          removeFromParent(nodes, path, 'body', incoming);
        };
      }
      transaction.oncomplete = function() {
        if(oldNode) {
          this._emit('change', {
            path: path,
            origin: incoming ? 'remote' : 'window',
            oldValue: oldNode.body,
            newValue: undefined
          });
        }
        if(! incoming) {
          this._recordChange(path, { action: 'DELETE' });
        }
        promise.fulfill(200);
      }.bind(this);
      return promise;
    },

    setRevision: function(path, revision) {
      return this.setRevisions([[path, revision]]);
    },

    setRevisions: function(revs) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      revs.forEach(function(rev) {
        var nodes = transaction.objectStore('nodes');
        nodes.get(rev[0]).onsuccess = function(event) {
          var node = event.target.result || makeNode(rev[0]);
          node.revision = rev[1];
          nodes.put(node).onsuccess = function() {
            addToParent(nodes, rev[0], 'cached');
          };
        };
      });
      transaction.oncomplete = function() {
        promise.fulfill();
      };
      return promise;
    },

    getRevision: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var rev;
      transaction.objectStore('nodes').
        get(path).onsuccess = function(evt) {
          if(evt.target.result) {
            rev = evt.target.result.revision;
          }
        };
      transaction.oncomplete = function() {
        promise.fulfill(rev);
      };
      return promise;
    },

    getCached: function(path) {
      if(path[path.length - 1] != '/') {
        return this.get(path);
      }
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var nodes = transaction.objectStore('nodes');
      nodes.get(path).onsuccess = function(evt) {
        var node = evt.target.result || {};
        promise.fulfill(200, node.cached, node.contentType, node.revision);
      };
      return promise;
    },

    reset: function(callback) {
      var dbName = this.db.name;
      this.db.close();
      var self = this;
      RS.IndexedDB.clean(this.db.name, function() {
        RS.IndexedDB.open(dbName, function(other) {
          // hacky!
          self.db = other.db;
          callback(self);
        });
      });
    },

    _fireInitial: function() {
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var cursorReq = transaction.objectStore('nodes').openCursor();
      cursorReq.onsuccess = function(evt) {
        var cursor = evt.target.result;
        if(cursor) {
          var path = cursor.key;
          if(path.substr(-1) != '/') {
            this._emit('change', {
              path: path,
              origin: 'remote',
              oldValue: undefined,
              newValue: cursor.value.body
            });
          }
          cursor.continue();
        }
      }.bind(this);
    },

    _recordChange: function(path, attributes) {
      var transaction = this.db.transaction(['changes'], 'readwrite');
      var changes = transaction.objectStore('changes');
      var change;
      changes.get(path).onsuccess = function(evt) {
        change = evt.target.result || {};
        change.path = path;
        for(var key in attributes) {
          change[key] = attributes[key];
        }
        changes.put(change);
      };
      transaction.oncomplete = function() {
      };
    },

    clearChange: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['changes'], 'readwrite');
      var changes = transaction.objectStore('changes');
      changes.delete(path);
      transaction.oncomplete = function() {
        promise.fulfill();
      }
      return promise;
    },

    changesBelow: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['changes'], 'readonly');
      var cursorReq = transaction.objectStore('changes').
        openCursor(IDBKeyRange.lowerBound(path));
      var pl = path.length;
      var changes = [];
      cursorReq.onsuccess = function() {
        var cursor = cursorReq.result;
        if(cursor) {
          if(cursor.key.substr(0, pl) == path) {
            changes.push(cursor.value);
            cursor.continue();
          }
        }
      };
      transaction.oncomplete = function() {
        promise.fulfill(changes);
      };
      return promise;
    },

    setConflict: function(path, attributes) {
      var event = { path: path };
      for(var key in attributes) {
        event[key] = attributes[key];
      }
      this._recordChange(path, { conflict: attributes }).
        then(function() {
          // fire conflict once conflict has been recorded.
          this._emit('conflict', event);
        }.bind(this));
      event.resolve = function(resolution) {
        if(resolution == 'remote' || resolution == 'local') {
          attributes = resolution;
          this._recordChange(path, { conflict: attributes });
        } else {
          throw "Invalid resolution: " + resolution;
        }
      }.bind(this);
      event.resolve = makeResolver(local, path);
    }

  };

  var DB_VERSION = 2;
  RS.IndexedDB.open = function(name, callback) {
    var dbOpen = indexedDB.open(name, DB_VERSION);
    dbOpen.onerror = function() {
      console.error("Opening db failed: ", dbOpen.errorCode);
    };
    dbOpen.onupgradeneeded = function(event) {
      var db = dbOpen.result;
      if(event.oldVersion != 1) {
        db.createObjectStore('nodes', { keyPath: 'path' });
      }
      db.createObjectStore('changes', { keyPath: 'path' });
    }
    dbOpen.onsuccess = function() {
      callback(dbOpen.result);
    };
  };

  RS.IndexedDB.clean = function(databaseName, callback) {
    var req = indexedDB.deleteDatabase(databaseName);
    req.onsuccess = function() {
      console.log('done removing db');
      callback();
    };
    req.onerror = function(evt) {
      console.error('failed to remove database "' + databaseName + '"', evt);
    };
  };

  RS.IndexedDB._rs_init = function(remoteStorage) {
    var promise = promising();
    remoteStorage.on('ready', function() {
      promise.then(function() {
        remoteStorage.local._fireInitial();
      });
    });
    RS.IndexedDB.open(DEFAULT_DB_NAME, function(db) {
      DEFAULT_DB = db;
      promise.fulfill();
    });
    return promise;
  };

  RS.IndexedDB._rs_supported = function() {
    return 'indexedDB' in global;
  }

  RS.IndexedDB._rs_cleanup = function() {
    var promise = promising();
    RS.IndexedDB.clean(DEFAULT_DB_NAME, function() {
      promise.fulfill();
    });
    return promise;
  }

})(this);
<commit_msg>indexeddb: close database before attempting to delete it<commit_after>(function(global) {

  /**
   * Class: RemoteStorage.IndexedDB
   *
   *
   * IndexedDB Interface
   * -------------------
   *
   * This file exposes a get/put/delete interface, accessing data in an indexedDB.
   *
   * There are multiple parts to this interface:
   *
   *   - The RemoteStorage integration:
   *     - RemoteStorage.IndexedDB._rs_supported() determines if indexedDB support
   *       is available. If it isn't, RemoteStorage won't initialize the feature.
   *     - RemoteStorage.IndexedDB._rs_init() initializes the feature. It returns
   *       a promise that is fulfilled as soon as the database has been opened and
   *       migrated.
   *
   *   - The storage interface (RemoteStorage.IndexedDB object):
   *     - Usually this is accessible via "remoteStorage.local"
   *     - #get() takes a path and returns a promise.
   *     - #put() takes a path, body and contentType and also returns a promise.
   *       In addition it also takes a 'incoming' flag, which indicates that the
   *       change is not fresh, but synchronized from remote.
   *     - #delete() takes a path and also returns a promise. It also supports
   *       the 'incoming' flag described for #put().
   *     - #on('change', ...) events, being fired whenever something changes in
   *       the storage. Change events roughly follow the StorageEvent pattern.
   *       They have "oldValue" and "newValue" properties, which can be used to
   *       distinguish create/update/delete operations and analyze changes in
   *       change handlers. In addition they carry a "origin" property, which
   *       is either "window" or "remote". "remote" events are fired whenever the
   *       "incoming" flag is passed to #put() or #delete(). This is usually done
   *       by RemoteStorage.Sync.
   *
   *   - The revision interface (also on RemoteStorage.IndexedDB object):
   *     - #setRevision(path, revision) sets the current revision for the given
   *       path. Revisions are only generated by the remotestorage server, so
   *       this is usually done from RemoteStorage.Sync once a pending change
   *       has been pushed out.
   *     - #setRevisions(revisions) takes path/revision pairs in the form:
   *       [[path1, rev1], [path2, rev2], ...] and updates all revisions in a
   *       single transaction.
   *     - #getRevision(path) returns the currently stored revision for the given
   *       path.
   *
   *   - The changes interface (also on RemoteStorage.IndexedDB object):
   *     - Used to record local changes between sync cycles.
   *     - Changes are stored in a separate ObjectStore called "changes".
   *     - #_recordChange() records a change and is called by #put() and #delete(),
   *       given the "incoming" flag evaluates to false. It is private andshould
   *       never be used from the outside.
   *     - #changesBelow() takes a path and returns a promise that will be fulfilled
   *       with an Array of changes that are pending for the given path or below.
   *       This is usually done in a sync cycle to push out pending changes.
   *     - #clearChange removes the change for a given path. This is usually done
   *       RemoteStorage.Sync once a change has successfully been pushed out.
   *     - #setConflict sets conflict attributes on a change. It also fires the
   *       "conflict" event.
   *     - #on('conflict', ...) event. Conflict events usually have the following
   *       attributes: path, localAction and remoteAction. Both actions are either
   *       "PUT" or "DELETE". They also bring a "resolve" method, which can be
   *       called with either of the strings "remote" and "local" to mark the
   *       conflict as resolved. The actual resolution will usually take place in
   *       the next sync cycle.
   */

  var RS = RemoteStorage;

  var DEFAULT_DB_NAME = 'remotestorage';
  var DEFAULT_DB;

  function keepDirNode(node) {
    return Object.keys(node.body).length > 0 ||
      Object.keys(node.cached).length > 0;
  }

  function removeFromParent(nodes, path, key) {
    var parts = path.match(/^(.*\/)([^\/]+\/?)$/);
    if(parts) {
      var dirname = parts[1], basename = parts[2];
      nodes.get(dirname).onsuccess = function(evt) {
        var node = evt.target.result;
        delete node[key][basename];
        if(keepDirNode(node)) {
          nodes.put(node);
        } else {
          nodes.delete(node.path).onsuccess = function() {
            if(dirname != '/') {
              removeFromParent(nodes, dirname, key);
            }
          };
        }
      };
    }
  }

  function makeNode(path) {
    var node = { path: path };
    if(path[path.length - 1] == '/') {
      node.body = {};
      node.cached = {};
      node.contentType = 'application/json';
    }
    return node;
  }

  function addToParent(nodes, path, key) {
    var parts = path.match(/^(.*\/)([^\/]+\/?)$/);
    if(parts) {
      var dirname = parts[1], basename = parts[2];
      nodes.get(dirname).onsuccess = function(evt) {
        var node = evt.target.result || makeNode(dirname);
        node[key][basename] = true;
        nodes.put(node).onsuccess = function() {
          if(dirname != '/') {
            addToParent(nodes, dirname, key);
          }
        };
      };
    }
  }

  RS.IndexedDB = function(database) {
    this.db = database || DEFAULT_DB;
    RS.eventHandling(this, 'change', 'conflict');
  };
  RS.IndexedDB.prototype = {

    get: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var nodes = transaction.objectStore('nodes');
      var nodeReq = nodes.get(path);
      var node;
      nodeReq.onsuccess = function() {
        node = nodeReq.result;
      };
      transaction.oncomplete = function() {
        if(node) {
          promise.fulfill(200, node.body, node.contentType, node.revision);
        } else {
          promise.fulfill(404);
        }
      };
      return promise;
    },

    put: function(path, body, contentType, incoming) {
      var promise = promising();
      if(path[path.length - 1] == '/') { throw "Bad: don't PUT folders"; }
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      var nodes = transaction.objectStore('nodes');
      var oldNode;
      nodes.get(path).onsuccess = function(evt) {
        oldNode = evt.target.result;
        var node = {
          path: path, contentType: contentType, body: body
        };
        nodes.put(node).onsuccess = function() { addToParent(nodes, path, 'body'); };
      };
      transaction.oncomplete = function() {
        this._emit('change', {
          path: path,
          origin: incoming ? 'remote' : 'window',
          oldValue: oldNode ? oldNode.body : undefined,
          newValue: body
        });
        if(! incoming) {
          this._recordChange(path, { action: 'PUT' });
        }
        promise.fulfill(200);
      }.bind(this);
      return promise;
    },

    delete: function(path, incoming) {
      var promise = promising();
      if(path[path.length - 1] == '/') { throw "Bad: don't DELETE folders"; }
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      var nodes = transaction.objectStore('nodes');
      var oldNode;
      nodes.get(path).onsuccess = function(evt) {
        oldNode = evt.target.result;
        nodes.delete(path).onsuccess = function() {
          removeFromParent(nodes, path, 'body', incoming);
        };
      }
      transaction.oncomplete = function() {
        if(oldNode) {
          this._emit('change', {
            path: path,
            origin: incoming ? 'remote' : 'window',
            oldValue: oldNode.body,
            newValue: undefined
          });
        }
        if(! incoming) {
          this._recordChange(path, { action: 'DELETE' });
        }
        promise.fulfill(200);
      }.bind(this);
      return promise;
    },

    setRevision: function(path, revision) {
      return this.setRevisions([[path, revision]]);
    },

    setRevisions: function(revs) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readwrite');
      revs.forEach(function(rev) {
        var nodes = transaction.objectStore('nodes');
        nodes.get(rev[0]).onsuccess = function(event) {
          var node = event.target.result || makeNode(rev[0]);
          node.revision = rev[1];
          nodes.put(node).onsuccess = function() {
            addToParent(nodes, rev[0], 'cached');
          };
        };
      });
      transaction.oncomplete = function() {
        promise.fulfill();
      };
      return promise;
    },

    getRevision: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var rev;
      transaction.objectStore('nodes').
        get(path).onsuccess = function(evt) {
          if(evt.target.result) {
            rev = evt.target.result.revision;
          }
        };
      transaction.oncomplete = function() {
        promise.fulfill(rev);
      };
      return promise;
    },

    getCached: function(path) {
      if(path[path.length - 1] != '/') {
        return this.get(path);
      }
      var promise = promising();
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var nodes = transaction.objectStore('nodes');
      nodes.get(path).onsuccess = function(evt) {
        var node = evt.target.result || {};
        promise.fulfill(200, node.cached, node.contentType, node.revision);
      };
      return promise;
    },

    reset: function(callback) {
      var dbName = this.db.name;
      this.db.close();
      var self = this;
      RS.IndexedDB.clean(this.db.name, function() {
        RS.IndexedDB.open(dbName, function(other) {
          // hacky!
          self.db = other.db;
          callback(self);
        });
      });
    },

    _fireInitial: function() {
      var transaction = this.db.transaction(['nodes'], 'readonly');
      var cursorReq = transaction.objectStore('nodes').openCursor();
      cursorReq.onsuccess = function(evt) {
        var cursor = evt.target.result;
        if(cursor) {
          var path = cursor.key;
          if(path.substr(-1) != '/') {
            this._emit('change', {
              path: path,
              origin: 'remote',
              oldValue: undefined,
              newValue: cursor.value.body
            });
          }
          cursor.continue();
        }
      }.bind(this);
    },

    _recordChange: function(path, attributes) {
      var transaction = this.db.transaction(['changes'], 'readwrite');
      var changes = transaction.objectStore('changes');
      var change;
      changes.get(path).onsuccess = function(evt) {
        change = evt.target.result || {};
        change.path = path;
        for(var key in attributes) {
          change[key] = attributes[key];
        }
        changes.put(change);
      };
      transaction.oncomplete = function() {
      };
    },

    clearChange: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['changes'], 'readwrite');
      var changes = transaction.objectStore('changes');
      changes.delete(path);
      transaction.oncomplete = function() {
        promise.fulfill();
      }
      return promise;
    },

    changesBelow: function(path) {
      var promise = promising();
      var transaction = this.db.transaction(['changes'], 'readonly');
      var cursorReq = transaction.objectStore('changes').
        openCursor(IDBKeyRange.lowerBound(path));
      var pl = path.length;
      var changes = [];
      cursorReq.onsuccess = function() {
        var cursor = cursorReq.result;
        if(cursor) {
          if(cursor.key.substr(0, pl) == path) {
            changes.push(cursor.value);
            cursor.continue();
          }
        }
      };
      transaction.oncomplete = function() {
        promise.fulfill(changes);
      };
      return promise;
    },

    setConflict: function(path, attributes) {
      var event = { path: path };
      for(var key in attributes) {
        event[key] = attributes[key];
      }
      this._recordChange(path, { conflict: attributes }).
        then(function() {
          // fire conflict once conflict has been recorded.
          this._emit('conflict', event);
        }.bind(this));
      event.resolve = function(resolution) {
        if(resolution == 'remote' || resolution == 'local') {
          attributes = resolution;
          this._recordChange(path, { conflict: attributes });
        } else {
          throw "Invalid resolution: " + resolution;
        }
      }.bind(this);
      event.resolve = makeResolver(local, path);
    },

    closeDB: function() {
      this.db.close();
    }

  };

  var DB_VERSION = 2;
  RS.IndexedDB.open = function(name, callback) {
    var dbOpen = indexedDB.open(name, DB_VERSION);
    dbOpen.onerror = function() {
      console.error("Opening db failed: ", dbOpen.errorCode);
    };
    dbOpen.onupgradeneeded = function(event) {
      var db = dbOpen.result;
      if(event.oldVersion != 1) {
        db.createObjectStore('nodes', { keyPath: 'path' });
      }
      db.createObjectStore('changes', { keyPath: 'path' });
    }
    dbOpen.onsuccess = function() {
      callback(dbOpen.result);
    };
  };

  RS.IndexedDB.clean = function(databaseName, callback) {
    var req = indexedDB.deleteDatabase(databaseName);
    req.onsuccess = function() {
      console.log('done removing db');
      callback();
    };
    req.onerror = function(evt) {
      console.error('failed to remove database "' + databaseName + '"', evt);
    };
  };

  RS.IndexedDB._rs_init = function(remoteStorage) {
    var promise = promising();
    remoteStorage.on('ready', function() {
      promise.then(function() {
        remoteStorage.local._fireInitial();
      });
    });
    RS.IndexedDB.open(DEFAULT_DB_NAME, function(db) {
      DEFAULT_DB = db;
      promise.fulfill();
    });
    return promise;
  };

  RS.IndexedDB._rs_supported = function() {
    return 'indexedDB' in global;
  }

  RS.IndexedDB._rs_cleanup = function(remoteStorage) {
    if(remoteStorage.local) {
      remoteStorage.local.closeDB();
    }
    var promise = promising();
    RS.IndexedDB.clean(DEFAULT_DB_NAME, function() {
      promise.fulfill();
    });
    return promise;
  }

})(this);
<|endoftext|>


<commit_before>$(document).ready(function() {
    // Basket page add remove quantity

    function getValues(selector) {
      var count = parseInt(selector.closest('.basket-row').find('.basket-quantity-value').text(), 10);

      return {
          count: count
      }
    }

    // Handles the click events
    function handleCount(e) {
      e.preventDefault();
      var currentCount = getValues($(this)).count;

      if ($(this).hasClass('btn-up')) {
        currentCount += 1;
      }
      else {
        if (currentCount > 1) {
            currentCount -= 1;
        }
      }
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "update",
                "quantity": currentCount,
                "line": $(this).closest('.basket-row').data("lineid")
            },
            success: updateBaketView
        })
    }

    function handleDelete(e){
        e.preventDefault();
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "delete",
                "line": $(this).closest('.basket-row').data("lineid"),
                "campaign": $(this).closest('.basket-row').data("campaignid")
            },
            success:  updateBaketView
        })
    }
    function handleDiscountCode(e) {
        e.preventDefault();
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "discount",
                "discount_code": $('#discount-code').val()
            },
            success: updateBaketView
        })
    }

    function updateBaketView(data) {
        $("section.basket").html(data.html);
        $(".product-counter").html(data.nav_counter);
        $(".basket-price-value").html(data.basket_total_price);
    }

    // Bind click event to buttons
    $('body').on('click', '.btn-up, .btn-down', handleCount);
    $('body').on('click', '.btn-delete-row', handleDelete);
    $('body').on('click', '.add-discount', handleDiscountCode);
    // Init tooltips
    $(document.body).tooltip({ selector: "[title]" });
});
<commit_msg>Remove existing tooltips on basket content update via ajax<commit_after>$(document).ready(function() {
    // Basket page add remove quantity

    function getValues(selector) {
      var count = parseInt(selector.closest('.basket-row').find('.basket-quantity-value').text(), 10);

      return {
          count: count
      }
    }

    // Handles the click events
    function handleCount(e) {
      e.preventDefault();
      var currentCount = getValues($(this)).count;

      if ($(this).hasClass('btn-up')) {
        currentCount += 1;
      }
      else {
        if (currentCount > 1) {
            currentCount -= 1;
        }
      }
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "update",
                "quantity": currentCount,
                "line": $(this).closest('.basket-row').data("lineid")
            },
            success: updateBaketView
        })
    }

    function handleDelete(e){
        e.preventDefault();
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "delete",
                "line": $(this).closest('.basket-row').data("lineid"),
                "campaign": $(this).closest('.basket-row').data("campaignid")
            },
            success:  updateBaketView
        })
    }
    function handleDiscountCode(e) {
        e.preventDefault();
        $.ajax({
            type: "POST",
            url: "",
            data: {
                "action": "discount",
                "discount_code": $('#discount-code').val()
            },
            success: updateBaketView
        })
    }

    function updateBaketView(data) {
        $('.tooltip').tooltip('destroy');
        $("section.basket").html(data.html);
        $(".product-counter").html(data.nav_counter);
        $(".basket-price-value").html(data.basket_total_price);
    }

    // Bind click event to buttons
    $('body').on('click', '.btn-up, .btn-down', handleCount);
    $('body').on('click', '.btn-delete-row', handleDelete);
    $('body').on('click', '.add-discount', handleDiscountCode);
    // Init tooltips
    $(document.body).tooltip({ selector: "[title]" });
});
<|endoftext|>


<commit_before>import { ScrollPane, WorkflowPane, DefaultDOMElement } from 'substance'
import { AbstractWriter } from '../util'
import TOCProvider from '../util/TOCProvider'
import TOC from './TOC'
import ReferenceManager from '../util/ReferenceManager'
import FigureManager from '../util/FigureManager'
import TableManager from '../util/TableManager'
import FootnoteManager from '../util/FootnoteManager'

export default class Editor extends AbstractWriter {

  didMount() {
    super.didMount()
    this.handleActions({
      'switchContext': this._switchContext
    })

    // HACK: we need to re-evaluate command states, now that the UI has mounted
    this.editorSession.commandManager._updateCommandStates(this.editorSession)

    DefaultDOMElement.getBrowserWindow().on('resize', this._showHideTOC, this)
    this.tocProvider.on('toc:updated', this._showHideTOC, this)
    this._showHideTOC()
  }

  didUpdate() {
    super.didUpdate()
    this._showHideTOC()
  }

  _dispose() {
    let doc = this.editorSession.getDocument()
    super._dispose()
    DefaultDOMElement.getBrowserWindow().off(this)
    this.tocProvider.off(this)
    delete doc.referenceManager
  }

  _initialize(props) {
    super._initialize(props)
    let editorSession = props.editorSession
    let doc = editorSession.getDocument()

    this.referenceManager = new ReferenceManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('references'),
      editorSession,
      pubMetaDbSession: props.pubMetaDbSession
    })

    // HACK: we need to expose referenceManager somehow, so it can be used in
    // the JATSExporter. We may want to consider including referenceManager in
    // TextureDocument instead.
    doc.referenceManager = this.referenceManager

    this.figureManager = new FigureManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('figures'),
      editorSession
    })
    this.tableManager = new TableManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('tables'),
      editorSession
    })
    this.footnoteManager = new FootnoteManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('footnotes'),
      editorSession
    })
  }

  getChildContext() {
    return Object.assign({}, super.getChildContext(), {
      referenceManager: this.referenceManager,
      figureManager: this.figureManager,
      tableManager: this.tableManager,
      footnoteManager: this.footnoteManager
    })
  }

  /*
    Switches the state of the context panel
  */
  _switchContext(state) {
    this.refs.contextSection.setState(state)
  }

  render($$) {
    let el = $$('div').addClass('sc-editor')
    el.append(
      this._renderMainSection($$),
      this._renderContextPane($$)
    )
    return el
  }

  _renderTOCPane($$) {
    let el = $$('div').addClass('se-toc-pane').ref('tocPane')
    el.append(
      $$('div').addClass('se-context-pane-content').append(
        $$(TOC)
      )
    )
    return el
  }

  _renderContextPane($$) {
    if (this.props.contextComponent) {
      let el = $$('div').addClass('se-context-pane')
      el.append(
        $$('div').addClass('se-context-pane-content').append(
          this.props.contextComponent
        )
      )
      return el
    }
  }

  _renderMainSection($$) {
    const configurator = this.getConfigurator()
    let mainSection = $$('div').addClass('se-main-section')
    mainSection.append(
      this._renderToolbar($$),
      $$('div').addClass('se-editor-section').append(
        this._renderTOCPane($$),
        this._renderContentPanel($$)
      ).ref('editorSection'),
      $$(WorkflowPane, {
        toolPanel: configurator.getToolPanel('workflow')
      })
    )
    return mainSection
  }

  _renderContentPanel($$) {
    const doc = this.editorSession.getDocument()
    const configurator = this.getConfigurator()
    const ManuscriptComponent = this.getComponent('manuscript')
    const Overlay = this.getComponent('overlay')
    // const ContextMenu = this.getComponent('context-menu')
    const Dropzones = this.componentRegistry.get('dropzones', 'strict')

    const article = doc.get('article')

    let contentPanel = $$(ScrollPane, {
      tocProvider: this.tocProvider,
      // scrollbarType: 'substance',
      scrollbarPosition: 'right',
      highlights: this.contentHighlights,
    }).ref('contentPanel')

    contentPanel.append(
      $$(ManuscriptComponent, {
        node: article,
        disabled: this.props.disabled
      }).ref('article'),
      $$(Overlay, {
        toolPanel: configurator.getToolPanel('main-overlay'),
        theme: 'dark'
      }),
      // $$(ContextMenu),
      $$(Dropzones)
    )
    return contentPanel
  }

  _scrollTo(nodeId) {
    this.refs.contentPanel.scrollTo(`[data-id="${nodeId}"]`)
  }

  tocEntrySelected(nodeId) {
    const node = this.doc.get(nodeId)
    const editorSession = this.getEditorSession()
    const nodeComponent = this.refs.contentPanel.find(`[data-id="${nodeId}"]`)
    if (nodeComponent) {
      // TODO: it needs to be easier to retrieve the surface
      let surface = nodeComponent.context.surface
      // There are cases when we can't set selection, e.g. for references
      if(surface) {
        editorSession.setSelection({
          type: 'property',
          path: node.getPath(),
          startOffset: 0,
          surfaceId: surface.id,
          containerId: surface.getContainerId()
        })
      }
      return this._scrollTo(nodeId)
    }
  }

  getConfigurator() {
    return this.props.editorSession.configurator
  }

  _showHideTOC() {
    let editorSectionWidth = this.refs.editorSection.el.width
    if (!this._isTOCVisible() || editorSectionWidth < 960) {
      this.el.addClass('sm-compact')
    } else {
      this.el.removeClass('sm-compact')
    }
  }

  _isTOCVisible() {
    let entries = this.tocProvider.getEntries()
    return entries.length >= 2
  }

  /*
    Exporter provided by Texture
  */
  _getExporter() {
    return this.context.exporter
  }

  _getTOCProvider() {
    let containerId = this._getBodyContentContainerId()
    let doc = this.editorSession.getDocument()
    return new TOCProvider(doc, {
      containerId: containerId
    })
  }

  _getBodyContentContainerId() {
    const doc = this.editorSession.getDocument()
    let bodyContent = doc.article.find('body-content')
    return bodyContent.id
  }
}
<commit_msg>Restore viewport and selection.<commit_after>import { ScrollPane, WorkflowPane, DefaultDOMElement } from 'substance'
import { AbstractWriter } from '../util'
import TOCProvider from '../util/TOCProvider'
import TOC from './TOC'
import ReferenceManager from '../util/ReferenceManager'
import FigureManager from '../util/FigureManager'
import TableManager from '../util/TableManager'
import FootnoteManager from '../util/FootnoteManager'

export default class Editor extends AbstractWriter {

  didMount() {
    super.didMount()
    this.handleActions({
      'switchContext': this._switchContext
    })

    // HACK: we need to re-evaluate command states, now that the UI has mounted
    this.editorSession.commandManager._updateCommandStates(this.editorSession)

    DefaultDOMElement.getBrowserWindow().on('resize', this._showHideTOC, this)
    this.tocProvider.on('toc:updated', this._showHideTOC, this)
    this._showHideTOC()
    this._restoreViewport()
  }

  didUpdate() {
    super.didUpdate()
    this._showHideTOC()
    this._restoreViewport()
  }

  getViewport() {
    return {
      x: this.refs.contentPanel.getScrollPosition()
    }
  }

  _restoreViewport() {
    if (this.props.viewport) {
      this.refs.contentPanel.setScrollPosition(this.props.viewport.x)
    }

    // HACK: This should work without a timeout, however it seems that
    // Editor.didMount is called earlier than the didMounts of the different
    // surfaces which do the surface registering, required here.
    setTimeout(() => {
      // We also use this place to rerender the selection
      let editorSession = this.editorSession
      let focusedSurface = editorSession.getFocusedSurface()
      if (focusedSurface) {
        focusedSurface.rerenderDOMSelection()
      }
    })
  }

  _dispose() {
    let doc = this.editorSession.getDocument()
    super._dispose()
    DefaultDOMElement.getBrowserWindow().off(this)
    this.tocProvider.off(this)
    delete doc.referenceManager
  }

  _initialize(props) {
    super._initialize(props)
    let editorSession = props.editorSession
    let doc = editorSession.getDocument()

    this.referenceManager = new ReferenceManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('references'),
      editorSession,
      pubMetaDbSession: props.pubMetaDbSession
    })

    // HACK: we need to expose referenceManager somehow, so it can be used in
    // the JATSExporter. We may want to consider including referenceManager in
    // TextureDocument instead.
    doc.referenceManager = this.referenceManager

    this.figureManager = new FigureManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('figures'),
      editorSession
    })
    this.tableManager = new TableManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('tables'),
      editorSession
    })
    this.footnoteManager = new FootnoteManager({
      labelGenerator: editorSession.getConfigurator().getLabelGenerator('footnotes'),
      editorSession
    })
  }

  getChildContext() {
    return Object.assign({}, super.getChildContext(), {
      referenceManager: this.referenceManager,
      figureManager: this.figureManager,
      tableManager: this.tableManager,
      footnoteManager: this.footnoteManager
    })
  }

  /*
    Switches the state of the context panel
  */
  _switchContext(state) {
    this.refs.contextSection.setState(state)
  }

  render($$) {
    let el = $$('div').addClass('sc-editor')
    el.append(
      this._renderMainSection($$),
      this._renderContextPane($$)
    )
    return el
  }

  _renderTOCPane($$) {
    let el = $$('div').addClass('se-toc-pane').ref('tocPane')
    el.append(
      $$('div').addClass('se-context-pane-content').append(
        $$(TOC)
      )
    )
    return el
  }

  _renderContextPane($$) {
    if (this.props.contextComponent) {
      let el = $$('div').addClass('se-context-pane')
      el.append(
        $$('div').addClass('se-context-pane-content').append(
          this.props.contextComponent
        )
      )
      return el
    }
  }

  _renderMainSection($$) {
    const configurator = this.getConfigurator()
    let mainSection = $$('div').addClass('se-main-section')
    mainSection.append(
      this._renderToolbar($$),
      $$('div').addClass('se-editor-section').append(
        this._renderTOCPane($$),
        this._renderContentPanel($$)
      ).ref('editorSection'),
      $$(WorkflowPane, {
        toolPanel: configurator.getToolPanel('workflow')
      })
    )
    return mainSection
  }

  _renderContentPanel($$) {
    const doc = this.editorSession.getDocument()
    const configurator = this.getConfigurator()
    const ManuscriptComponent = this.getComponent('manuscript')
    const Overlay = this.getComponent('overlay')
    // const ContextMenu = this.getComponent('context-menu')
    const Dropzones = this.componentRegistry.get('dropzones', 'strict')

    const article = doc.get('article')

    let contentPanel = $$(ScrollPane, {
      tocProvider: this.tocProvider,
      // scrollbarType: 'substance',
      scrollbarPosition: 'right',
      highlights: this.contentHighlights,
    }).ref('contentPanel')

    contentPanel.append(
      $$(ManuscriptComponent, {
        node: article,
        disabled: this.props.disabled
      }).ref('article'),
      $$(Overlay, {
        toolPanel: configurator.getToolPanel('main-overlay'),
        theme: 'dark'
      }),
      // $$(ContextMenu),
      $$(Dropzones)
    )
    return contentPanel
  }

  _scrollTo(nodeId) {
    this.refs.contentPanel.scrollTo(`[data-id="${nodeId}"]`)
  }

  tocEntrySelected(nodeId) {
    const node = this.doc.get(nodeId)
    const editorSession = this.getEditorSession()
    const nodeComponent = this.refs.contentPanel.find(`[data-id="${nodeId}"]`)
    if (nodeComponent) {
      // TODO: it needs to be easier to retrieve the surface
      let surface = nodeComponent.context.surface
      // There are cases when we can't set selection, e.g. for references
      if(surface) {
        editorSession.setSelection({
          type: 'property',
          path: node.getPath(),
          startOffset: 0,
          surfaceId: surface.id,
          containerId: surface.getContainerId()
        })
      }
      return this._scrollTo(nodeId)
    }
  }

  getConfigurator() {
    return this.props.editorSession.configurator
  }

  _showHideTOC() {
    let editorSectionWidth = this.refs.editorSection.el.width
    if (!this._isTOCVisible() || editorSectionWidth < 960) {
      this.el.addClass('sm-compact')
    } else {
      this.el.removeClass('sm-compact')
    }
  }

  _isTOCVisible() {
    let entries = this.tocProvider.getEntries()
    return entries.length >= 2
  }

  /*
    Exporter provided by Texture
  */
  _getExporter() {
    return this.context.exporter
  }

  _getTOCProvider() {
    let containerId = this._getBodyContentContainerId()
    let doc = this.editorSession.getDocument()
    return new TOCProvider(doc, {
      containerId: containerId
    })
  }

  _getBodyContentContainerId() {
    const doc = this.editorSession.getDocument()
    let bodyContent = doc.article.find('body-content')
    return bodyContent.id
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import {Platform} from 'react-native';
import {StateNavigator} from 'navigation';
import {NavigationStack} from 'navigation-react-native';
import Grid from './Grid';
import Detail from './Detail';

const colors = [
  'maroon', 'red', 'crimson', 'orange', 'brown', 'sienna', 'olive',
  'purple', 'fuchsia', 'indigo', 'green', 'navy', 'blue', 'teal', 'black'
];

export default () => {
  const stateNavigator = new StateNavigator([
    {key: 'grid', route: ''},
    {key: 'detail', route: '{color}', trackCrumbTrail: true},
  ]);

  const {grid, detail} = stateNavigator.states;
  grid.renderScene = () => <Grid colors={colors} />;
  detail.renderScene = ({color}) => <Detail colors={colors} color={color} />;

  detail.truncateCrumbTrail = (state, data, crumbs) => (
    crumbs.slice(-1)[0].state === detail ? crumbs.slice(0, -1) : crumbs
  );

  const buildStartUrl = url => {
    const {state, data} = stateNavigator.parseLink(url);
    return stateNavigator.fluent()
      .navigate('grid')
      .navigate(state.key, data).url;
  };
  stateNavigator.configure(stateNavigator, new NavigationStack.HistoryManager(buildStartUrl));

  if (Platform.OS !== 'web') stateNavigator.navigate('grid');
  else stateNavigator.start();

  return stateNavigator;
}
<commit_msg>Configured history manager only on web<commit_after>import React from 'react';
import {Platform} from 'react-native';
import {StateNavigator} from 'navigation';
import {NavigationStack} from 'navigation-react-native';
import Grid from './Grid';
import Detail from './Detail';

const colors = [
  'maroon', 'red', 'crimson', 'orange', 'brown', 'sienna', 'olive',
  'purple', 'fuchsia', 'indigo', 'green', 'navy', 'blue', 'teal', 'black'
];

export default () => {
  const stateNavigator = new StateNavigator([
    {key: 'grid', route: ''},
    {key: 'detail', route: '{color}', trackCrumbTrail: true},
  ]);

  const {grid, detail} = stateNavigator.states;
  grid.renderScene = () => <Grid colors={colors} />;
  detail.renderScene = ({color}) => <Detail colors={colors} color={color} />;

  detail.truncateCrumbTrail = (state, data, crumbs) => (
    crumbs.slice(-1)[0].state === detail ? crumbs.slice(0, -1) : crumbs
  );

  if (Platform.OS === 'web') {
    const buildStartUrl = url => {
      const {state, data} = stateNavigator.parseLink(url);
      return stateNavigator.fluent()
        .navigate('grid')
        .navigate(state.key, data).url;
    };
    stateNavigator.configure(stateNavigator, new NavigationStack.HistoryManager(buildStartUrl));
  }

  if (Platform.OS !== 'web') stateNavigator.navigate('grid');
  else stateNavigator.start();

  return stateNavigator;
}
<|endoftext|>


<commit_before>(function($) {
  /*
    ======== A Handy Little QUnit Reference ========
    http://api.qunitjs.com/

    Test methods:
      module(name, {[setup][ ,teardown]})
      test(name, callback)
      expect(numberOfAssertions)
      stop(increment)
      start(decrement)
    Test assertions:
      ok(value, [message])
      equal(actual, expected, [message])
      notEqual(actual, expected, [message])
      deepEqual(actual, expected, [message])
      notDeepEqual(actual, expected, [message])
      strictEqual(actual, expected, [message])
      notStrictEqual(actual, expected, [message])
      throws(block, [expected], [message])
  */

  module('No option', {
    setup: function() {
      this.h1 = $('h1');
      this.h1_text = this.h1.text();
      this.h2 = $('h2');
      this.h2_text = this.h2.text();
    }
  });

  test('is chainable', function() {
    expect(2);
    strictEqual(this.h1.kerning(), this.h1);
    strictEqual(this.h2.kerning(), this.h2);
  });

  test('元のテキストと同じように読める', function() {
    expect(2);
    strictEqual(this.h1.kerning().text(), this.h1_text);
    strictEqual(this.h2.kerning().text(), this.h2_text);
  });

  test('オプションなしの場合、約物のみカーニングする', function() {
    expect(2);
    strictEqual(this.h1.kerning().find('[data-kerned]').length, 2);
    strictEqual(this.h2.kerning().find('[data-kerned]').length, 0);
  });

  module('With option', {
    setup: function() {
      this.h1 = $('h1');
      this.h2 = $('h2');
      this.h3 = $('h3');
      this.kerningdata = [
        {
          "kerning":{
            "あ":{"L":-0.1,"R":-0.1},
            "い":{"L":-0.1,"R":-0.08},
            "う":{"L":-0.13,"R":-0.16},
            "え":{"L":-0.1,"R":-0.07},
            "お":{"L":-0.09,"R":-0.04}
          }
        }
      ];
    }
  });

  test('オプションでカーニングデータを指定できる', function() {
    expect(3);
    strictEqual(this.h1.kerning({data:this.kerningdata}).find('[data-kerned]').length, 0);
    strictEqual(this.h2.kerning({data:this.kerningdata}).find('[data-kerned]').length, 1);
    strictEqual(this.h3.kerning({data:this.kerningdata}).find('[data-kerned]').length, 5);
  });

  module('With option async', {
    setup: function() {
      this.p = $('#paragraph');
    }
  });

  asyncTest( "jsonファイルを読み込んで使える", function() {
    expect(1);
    var target = this.p;
    $.getJSON("../data/mplus-2m-regular.json" , function(_data){
      start();
      strictEqual(target.kerning({data:_data}).find('[data-kerned]').length, 14);
    });
  });

  // test('Can destroy', function() {
  //   expect(1);
  //   strictEqual(this.elems.html(), this.elems.kerning().html(), 'should be kerned with no options');
  // });

}(jQuery));
<commit_msg>omit expect()<commit_after>(function($) {
  /*
    ======== A Handy Little QUnit Reference ========
    http://api.qunitjs.com/

    Test methods:
      module(name, {[setup][ ,teardown]})
      test(name, callback)
      expect(numberOfAssertions)
      stop(increment)
      start(decrement)
    Test assertions:
      ok(value, [message])
      equal(actual, expected, [message])
      notEqual(actual, expected, [message])
      deepEqual(actual, expected, [message])
      notDeepEqual(actual, expected, [message])
      strictEqual(actual, expected, [message])
      notStrictEqual(actual, expected, [message])
      throws(block, [expected], [message])
  */

  module('No option', {
    setup: function() {
      this.h1 = $('h1');
      this.h1_text = this.h1.text();
      this.h2 = $('h2');
      this.h2_text = this.h2.text();
    }
  });

  test('is chainable', 2, function() {
    strictEqual(this.h1.kerning(), this.h1);
    strictEqual(this.h2.kerning(), this.h2);
  });

  test('元のテキストと同じように読める', 2, function() {
    strictEqual(this.h1.kerning().text(), this.h1_text);
    strictEqual(this.h2.kerning().text(), this.h2_text);
  });

  test('オプションなしの場合、約物のみカーニングする', 2, function() {
    strictEqual(this.h1.kerning().find('[data-kerned]').length, 2);
    strictEqual(this.h2.kerning().find('[data-kerned]').length, 0);
  });

  module('With option', {
    setup: function() {
      this.h1 = $('h1');
      this.h2 = $('h2');
      this.h3 = $('h3');
      this.kerningdata = [
        {
          "kerning":{
            "あ":{"L":-0.1,"R":-0.1},
            "い":{"L":-0.1,"R":-0.08},
            "う":{"L":-0.13,"R":-0.16},
            "え":{"L":-0.1,"R":-0.07},
            "お":{"L":-0.09,"R":-0.04}
          }
        }
      ];
    }
  });

  test('オプションでカーニングデータを指定できる', 3, function() {
    strictEqual(this.h1.kerning({data:this.kerningdata}).find('[data-kerned]').length, 0);
    strictEqual(this.h2.kerning({data:this.kerningdata}).find('[data-kerned]').length, 1);
    strictEqual(this.h3.kerning({data:this.kerningdata}).find('[data-kerned]').length, 5);
  });

  module('With option async', {
    setup: function() {
      this.p = $('#paragraph');
    }
  });

  asyncTest( "jsonファイルを読み込んで使える", 1, function() {
    var target = this.p;
    $.getJSON("../data/mplus-2m-regular.json" , function(_data){
      start();
      strictEqual(target.kerning({data:_data}).find('[data-kerned]').length, 14);
    });
  });

  // test('Can destroy', 1, function() {
  //   strictEqual(this.elems.html(), this.elems.kerning().html(), 'should be kerned with no options');
  // });

}(jQuery));
<|endoftext|>


<commit_before>pc.extend(pc.input, function () {
    /**
    * @name pc.input.Hmd
    * @description Input Device for a head mounted display
    * @param graphicsDevice The graphics device
    */
    var Hmd = function (graphicsDevice) {

        this._graphicsDevice = graphicsDevice;
        this._vrstate = null;

        this._device = null;
        this._sensor = null;

        this._rotation = new pc.Quat();
        this._position = new pc.Vec3();
    };

    Hmd.prototype = {
        /*
        * @name pc.input.Hmd#initialize
        * @description Initialize the Hmd object
        * @returns {pc.promise.Promise} Promise that is resolved when the HMD is ready to use
        */
        initialize: function () {
            var self = this;
            var p = null;

            p = new pc.promise.Promise(function (resolve, reject) {
                if (navigator.getVRDevices || navigator.mozGetVRDevices) {
                    var enumerateVRDevices = function (devices) {
                        var i, n;

                        for(i = 0, n = devices.length; i < n; i++) {
                            if (devices[i] instanceof HMDVRDevice) {
                                self._device = devices[i];
                            }
                            if (devices[i] instanceof PositionSensorVRDevice) {
                                self._sensor = devices[i];
                                if(self._sensor.resetSensor) {
                                    self._sensor.resetSensor();
                                }
                                if (self._sensor.zeroSensor) {
                                    self._sensor.zeroSensor();
                                }
                            }
                        }

                        if (!self._device || !self._sensor) {
                            reject("No HMD or HMD position sensor");
                        }

                        self.loaded = true;
                        resolve();
                    };

                    if (navigator.getVRDevices) {
                        navigator.getVRDevices().then(enumerateVRDevices);
                    } else if (navigator.mozGetVRDevices) {
                        navigator.mozGetVRDevices(enumerateVRDevices);
                    }

                } else {
                    reject("No HMD found")
                }
            });

            return p;
        },

        /**
        * @name pc.input.Hmd#poll
        * @description poll to get the state of any attached HMD devices. This should be called every frame.
        */
        poll: function () {
            if (this._device && this._sensor) {
                this._vrstate = this._sensor.getState();
            }
        },

        /**
        * @name pc.input.Hmd#fullscreen
        * @description Request for the browser to enter fullscreen mode and begin renderin
        */
        enterFullscreen: function () {
            // var el = document.body;
            var el = this._graphicsDevice.canvas;

            // watchout for camelcase weirdness
            if (el.webkitRequestFullscreen) {
                el.webkitRequestFullscreen({
                    vrDisplay: this._device
                });
            } else if (el.mozRequestFullScreen) {
                el.mozRequestFullScreen({
                    vrDisplay: this._device
                });
            }
        },

        exitFullscreen: function () {

        }
    };

    /**
    * @field
    * @type pc.Quat
    * @name pc.input.Hmd#rotation
    * @description The rotation quaternion for the HMD
    */
    Object.defineProperty(Hmd.prototype, 'rotation', {
        get: function() {
            if (this._sensor) {
                var ori;
                if (this._vrstate.hasOrientation || this._vrstate.orientation) {
                    ori = this._vrstate.orientation;
                } else {
                    ori = pc.Vec4.ZERO;
                }

                this._rotation.set(ori.x, ori.y, ori.z, ori.w);
            }
            return this._rotation;
        }
    });

    /**
    * @field
    * @type pc.Vec3
    * @name pc.input.Hmd#position
    * @description The position of the HMD
    */
    Object.defineProperty(Hmd.prototype, 'position', {
        get: function () {
            if (this._sensor) {
                var pos;
                if (this._vrstate.hasPosition || this._vrstate.position) {
                    pos = this._vrstate.position;
                } else {
                    pos = pc.Vec3.ZERO;
                }
                this._position.set(pos.x, pos.y, pos.z);
            }

            return this._position;
        }
    });

    return {
        Hmd: Hmd
    }
}());
<commit_msg>remove promises<commit_after>pc.extend(pc.input, function () {
    /**
    * @name pc.input.Hmd
    * @description Input Device for a head mounted display
    * @param graphicsDevice The graphics device
    */
    var Hmd = function (graphicsDevice) {
        this._graphicsDevice = graphicsDevice;
        this._vrstate = null;

        this._device = null;
        this._sensor = null;

        this._rotation = new pc.Quat();
        this._position = new pc.Vec3();
    };

    Hmd.prototype = {
        /*
        * @name pc.input.Hmd#initialize
        * @description Initialize the Hmd object
        */
        initialize: function (fn) {
            var self = this;

            if (navigator.getVRDevices || navigator.mozGetVRDevices) {
                var enumerateVRDevices = function (devices) {
                    for(var i = 0; i < devices.length; i++) {
                        if (devices[i] instanceof HMDVRDevice)
                            self._device = devices[i];

                        if (devices[i] instanceof PositionSensorVRDevice) {
                            self._sensor = devices[i];

                            if(self._sensor.resetSensor)
                                self._sensor.resetSensor();

                            if (self._sensor.zeroSensor)
                                self._sensor.zeroSensor();
                        }
                    }

                    if (! self._device || ! self._sensor)
                        return fn(new Error('No HMD or HMD position sensor'));

                    self.loaded = true;
                    fn(null);
                };

                if (navigator.getVRDevices) {
                    navigator.getVRDevices().then(enumerateVRDevices);
                } else if (navigator.mozGetVRDevices) {
                    navigator.mozGetVRDevices(enumerateVRDevices);
                }
            } else {
                fn(new Error('No HMD found'));
            }
        },

        /**
        * @name pc.input.Hmd#poll
        * @description poll to get the state of any attached HMD devices. This should be called every frame.
        */
        poll: function () {
            if (this._device && this._sensor) {
                this._vrstate = this._sensor.getState();
            }
        },

        /**
        * @name pc.input.Hmd#fullscreen
        * @description Request for the browser to enter fullscreen mode and begin renderin
        */
        enterFullscreen: function () {
            // var el = document.body;
            var el = this._graphicsDevice.canvas;

            // watchout for camelcase weirdness
            if (el.webkitRequestFullscreen) {
                el.webkitRequestFullscreen({
                    vrDisplay: this._device
                });
            } else if (el.mozRequestFullScreen) {
                el.mozRequestFullScreen({
                    vrDisplay: this._device
                });
            }
        }
    };

    /**
    * @field
    * @type pc.Quat
    * @name pc.input.Hmd#rotation
    * @description The rotation quaternion for the HMD
    */
    Object.defineProperty(Hmd.prototype, 'rotation', {
        get: function() {
            if (this._sensor) {
                var ori;
                if (this._vrstate.hasOrientation || this._vrstate.orientation) {
                    ori = this._vrstate.orientation;
                } else {
                    ori = pc.Vec4.ZERO;
                }

                this._rotation.set(ori.x, ori.y, ori.z, ori.w);
            }
            return this._rotation;
        }
    });

    /**
    * @field
    * @type pc.Vec3
    * @name pc.input.Hmd#position
    * @description The position of the HMD
    */
    Object.defineProperty(Hmd.prototype, 'position', {
        get: function () {
            if (this._sensor) {
                var pos;
                if (this._vrstate.hasPosition || this._vrstate.position) {
                    pos = this._vrstate.position;
                } else {
                    pos = pc.Vec3.ZERO;
                }
                this._position.set(pos.x, pos.y, pos.z);
            }

            return this._position;
        }
    });

    return {
        Hmd: Hmd
    };
}());
<|endoftext|>


<commit_before>var spawn = require("cross-spawn");
var fs = require("fs");
var kebabCase = require("lodash.kebabcase");
var path = require("path");
var util = require("util");

var INTERNAL = /^\./; // Match "./client", "../something", etc.
var EXTERNAL = /^[a-z\-0-9]+$/; // Match "react", "path", "fs", etc.

module.exports.check = function(request) {
  var namespaced = request.charAt(0) === "@";
  var dep = request.split("/")
    .slice(0, namespaced ? 2 : 1)
    .join("/")
  ;

  // Ignore relative modules, which aren't installed by NPM
  if (!dep.match(EXTERNAL) && !namespaced) {
    return;
  }

  try {
    var pkgPath = require.resolve(path.join(process.cwd(), "package.json"));
    var pkg = require(pkgPath);

    // Remove cached copy for future checks
    delete require.cache[pkgPath];
  } catch(e) {
    throw e;
  }

  var hasDep = pkg.dependencies && pkg.dependencies[dep];
  var hasDevDep = pkg.devDependencies && pkg.devDependencies[dep];

  // Bail early if we've already installed this dependency
  if (hasDep || hasDevDep) {
    return;
  }

  // Ignore linked modules
  try {
    var stats = fs.lstatSync(path.join(process.cwd(), "node_modules", dep));

    if (stats.isSymbolicLink()) {
      return;
    }
  } catch(e) {
    // Module exists in node_modules, but isn't symlinked
  }

  // Ignore NPM global modules (e.g. "path", "fs", etc.)
  try {
    var resolved = require.resolve(dep);

    // Global modules resolve to their name, not an actual path
    if (resolved.match(EXTERNAL)) {
      return;
    }
  } catch(e) {
    // Module is not resolveable
  }

  return dep;
}

module.exports.install = function install(dep, options) {
  if (!dep) {
    return;
  }

  var args = ["install"].concat([dep]).filter(Boolean);

  if (options) {
    for (option in options) {
      var arg = util.format("--%s", kebabCase(option));
      var value = options[option];

      if (value === false) {
        continue;
      }

      if (value === true) {
        args.push(arg);
      } else {
        args.push(util.format("%s='%s'", arg, value));
      }
    }
  }

  console.info("Installing `%s`...", dep);

  var output = spawn.sync("npm", args, { stdio: "inherit" });

  return output;
};
<commit_msg>installer.check returns undefined for falsey dependency<commit_after>var spawn = require("cross-spawn");
var fs = require("fs");
var kebabCase = require("lodash.kebabcase");
var path = require("path");
var util = require("util");

var INTERNAL = /^\./; // Match "./client", "../something", etc.
var EXTERNAL = /^[a-z\-0-9]+$/; // Match "react", "path", "fs", etc.

module.exports.check = function(request) {
  if (!request) {
    return;
  }

  var namespaced = request.charAt(0) === "@";
  var dep = request.split("/")
    .slice(0, namespaced ? 2 : 1)
    .join("/")
  ;

  // Ignore relative modules, which aren't installed by NPM
  if (!dep.match(EXTERNAL) && !namespaced) {
    return;
  }

  try {
    var pkgPath = require.resolve(path.join(process.cwd(), "package.json"));
    var pkg = require(pkgPath);

    // Remove cached copy for future checks
    delete require.cache[pkgPath];
  } catch(e) {
    throw e;
  }

  var hasDep = pkg.dependencies && pkg.dependencies[dep];
  var hasDevDep = pkg.devDependencies && pkg.devDependencies[dep];

  // Bail early if we've already installed this dependency
  if (hasDep || hasDevDep) {
    return;
  }

  // Ignore linked modules
  try {
    var stats = fs.lstatSync(path.join(process.cwd(), "node_modules", dep));

    if (stats.isSymbolicLink()) {
      return;
    }
  } catch(e) {
    // Module exists in node_modules, but isn't symlinked
  }

  // Ignore NPM global modules (e.g. "path", "fs", etc.)
  try {
    var resolved = require.resolve(dep);

    // Global modules resolve to their name, not an actual path
    if (resolved.match(EXTERNAL)) {
      return;
    }
  } catch(e) {
    // Module is not resolveable
  }

  return dep;
}

module.exports.install = function install(dep, options) {
  if (!dep) {
    return;
  }

  var args = ["install"].concat([dep]).filter(Boolean);

  if (options) {
    for (option in options) {
      var arg = util.format("--%s", kebabCase(option));
      var value = options[option];

      if (value === false) {
        continue;
      }

      if (value === true) {
        args.push(arg);
      } else {
        args.push(util.format("%s='%s'", arg, value));
      }
    }
  }

  console.info("Installing `%s`...", dep);

  var output = spawn.sync("npm", args, { stdio: "inherit" });

  return output;
};
<|endoftext|>


<commit_before>"use strict";

// local modules _must_ be explicitly mocked
jest.mock("../lib/get-packages-without-license");
jest.mock("../lib/verify-npm-package-access");
jest.mock("../lib/get-npm-username");
jest.mock("../lib/get-two-factor-auth-required");
jest.mock("../lib/get-unpublished-packages");
// FIXME: better mock for version command
jest.mock("../../version/lib/git-push");
jest.mock("../../version/lib/is-anything-committed");
jest.mock("../../version/lib/is-behind-upstream");
jest.mock("../../version/lib/remote-branch-exists");

// mocked or stubbed modules
const npmPublish = require("@lerna/npm-publish");
const PromptUtilities = require("@lerna/prompt");
const output = require("@lerna/output");
const checkWorkingTree = require("@lerna/check-working-tree");

// helpers
const loggingOutput = require("@lerna-test/logging-output");
const gitTag = require("@lerna-test/git-tag");
const initFixture = require("@lerna-test/init-fixture")(__dirname);

// file under test
const lernaPublish = require("@lerna-test/command-runner")(require("../command"));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("publish from-git", () => {
  it("publishes tagged packages", async () => {
    const cwd = await initFixture("normal");

    await gitTag(cwd, "v1.0.0");
    await lernaPublish(cwd)("from-git");

    // called from chained describeRef()
    expect(checkWorkingTree.throwIfUncommitted).toHaveBeenCalled();

    expect(PromptUtilities.confirm).toHaveBeenLastCalledWith(
      "Are you sure you want to publish these packages?"
    );
    expect(output.logged()).toMatch("Found 4 packages to publish:");
    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("publishes tagged independent packages", async () => {
    const cwd = await initFixture("independent");

    await Promise.all([
      gitTag(cwd, "package-1@1.0.0"),
      gitTag(cwd, "package-2@2.0.0"),
      gitTag(cwd, "package-3@3.0.0"),
      gitTag(cwd, "package-4@4.0.0"),
      gitTag(cwd, "package-5@5.0.0"),
    ]);
    await lernaPublish(cwd)("from-git");

    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("publishes packages matching custom --tag-version-prefix", async () => {
    const cwd = await initFixture("normal");

    await gitTag(cwd, "foo/1.0.0");
    await lernaPublish(cwd)("from-git", "--tag-version-prefix", "foo/");

    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("only publishes independent packages with matching tags", async () => {
    const cwd = await initFixture("independent");

    await gitTag(cwd, "package-3@3.0.0");
    await lernaPublish(cwd)("from-git");

    expect(output.logged()).toMatch("Found 1 package to publish:");
    expect(npmPublish.order()).toEqual(["package-3"]);
  });

  it("exits early when the current commit is not tagged", async () => {
    const cwd = await initFixture("normal");

    await lernaPublish(cwd)("from-git");

    expect(npmPublish).not.toHaveBeenCalled();

    const logMessages = loggingOutput("info");
    expect(logMessages).toContain("No tagged release found");
  });

  it("throws an error when uncommitted changes are present", async () => {
    checkWorkingTree.throwIfUncommitted.mockImplementationOnce(() => {
      throw new Error("uncommitted");
    });

    const cwd = await initFixture("normal");
    const command = lernaPublish(cwd)("from-git");

    await expect(command).rejects.toThrow("uncommitted");
    // notably different than the actual message, but good enough here
  });

  it("throws an error when --git-head is passed", async () => {
    const cwd = await initFixture("normal");
    const command = lernaPublish(cwd)("from-git", "--git-head", "deadbeef");

    await expect(command).rejects.toThrow(
      expect.objectContaining({
        prefix: "EGITHEAD",
      })
    );
  });
});
<commit_msg>test: Unmock collect-updates when testing publish from-git<commit_after>"use strict";

// we're actually testing integration with git
jest.unmock("@lerna/collect-updates");

// local modules _must_ be explicitly mocked
jest.mock("../lib/get-packages-without-license");
jest.mock("../lib/verify-npm-package-access");
jest.mock("../lib/get-npm-username");
jest.mock("../lib/get-two-factor-auth-required");
jest.mock("../lib/get-unpublished-packages");
// FIXME: better mock for version command
jest.mock("../../version/lib/git-push");
jest.mock("../../version/lib/is-anything-committed");
jest.mock("../../version/lib/is-behind-upstream");
jest.mock("../../version/lib/remote-branch-exists");

// mocked or stubbed modules
const npmPublish = require("@lerna/npm-publish");
const PromptUtilities = require("@lerna/prompt");
const output = require("@lerna/output");
const checkWorkingTree = require("@lerna/check-working-tree");

// helpers
const loggingOutput = require("@lerna-test/logging-output");
const gitTag = require("@lerna-test/git-tag");
const initFixture = require("@lerna-test/init-fixture")(__dirname);

// file under test
const lernaPublish = require("@lerna-test/command-runner")(require("../command"));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("publish from-git", () => {
  it("publishes tagged packages", async () => {
    const cwd = await initFixture("normal");

    await gitTag(cwd, "v1.0.0");
    await lernaPublish(cwd)("from-git");

    // called from chained describeRef()
    expect(checkWorkingTree.throwIfUncommitted).toHaveBeenCalled();

    expect(PromptUtilities.confirm).toHaveBeenLastCalledWith(
      "Are you sure you want to publish these packages?"
    );
    expect(output.logged()).toMatch("Found 4 packages to publish:");
    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("publishes tagged independent packages", async () => {
    const cwd = await initFixture("independent");

    await Promise.all([
      gitTag(cwd, "package-1@1.0.0"),
      gitTag(cwd, "package-2@2.0.0"),
      gitTag(cwd, "package-3@3.0.0"),
      gitTag(cwd, "package-4@4.0.0"),
      gitTag(cwd, "package-5@5.0.0"),
    ]);
    await lernaPublish(cwd)("from-git");

    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("publishes packages matching custom --tag-version-prefix", async () => {
    const cwd = await initFixture("normal");

    await gitTag(cwd, "foo/1.0.0");
    await lernaPublish(cwd)("from-git", "--tag-version-prefix", "foo/");

    expect(npmPublish.order()).toEqual([
      "package-1",
      "package-3",
      "package-4",
      "package-2",
      // package-5 is private
    ]);
  });

  it("only publishes independent packages with matching tags", async () => {
    const cwd = await initFixture("independent");

    await gitTag(cwd, "package-3@3.0.0");
    await lernaPublish(cwd)("from-git");

    expect(output.logged()).toMatch("Found 1 package to publish:");
    expect(npmPublish.order()).toEqual(["package-3"]);
  });

  it("exits early when the current commit is not tagged", async () => {
    const cwd = await initFixture("normal");

    await lernaPublish(cwd)("from-git");

    expect(npmPublish).not.toHaveBeenCalled();

    const logMessages = loggingOutput("info");
    expect(logMessages).toContain("No tagged release found");
  });

  it("throws an error when uncommitted changes are present", async () => {
    checkWorkingTree.throwIfUncommitted.mockImplementationOnce(() => {
      throw new Error("uncommitted");
    });

    const cwd = await initFixture("normal");
    const command = lernaPublish(cwd)("from-git");

    await expect(command).rejects.toThrow("uncommitted");
    // notably different than the actual message, but good enough here
  });

  it("throws an error when --git-head is passed", async () => {
    const cwd = await initFixture("normal");
    const command = lernaPublish(cwd)("from-git", "--git-head", "deadbeef");

    await expect(command).rejects.toThrow(
      expect.objectContaining({
        prefix: "EGITHEAD",
      })
    );
  });
});
<|endoftext|>


<commit_before>/*jshint mocha: true, -W030 */
var chai = require('chai');
var engineer = require('../lib/machine');
var sinon = require('sinon');

chai.use(require('sinon-chai'));

// Reference chai's `expect()``
var expect = chai.expect;

var apply = function (options) {
  return function () {
    engineer(options);
  };
};

describe('engineer(options)', function () {
  beforeEach(function () {
    this.sinon = sinon.sandbox.create();
  });

  beforeEach(function () {
    this.gate = engineer({
      states: {
        closed: ['open'],
        open: ['closed']
      },
      default: 'closed'
    });
  });

  afterEach(function () {
    this.sinon.restore();
  });

  it('throws if not passed options', function () {
    expect(apply()).to.throw('Expected options');
  });

  it('throws if no state transitions are specified', function () {
    expect(apply({default: 'open'})).to.throw('Expected map of state transitions');
  });

  it('throws if no default state is specified', function () {
    expect(apply({
      states: {
        closed: ['open'],
        open: ['closed']
      }
    })).to.throw('Expected default state');
  });

  it('throws if default state is unknown', function () {
    expect(apply({
      states: {
        closed: ['open'],
        open: ['closed']
      },
      default: 'half-open'
    })).to.throw(/^Invalid default state:/);
  });

  it('throws if state transitions to unknown state', function () {
    expect(apply({
      states: {
        closed: ['open', 'half-open'],
        open: ['closed']
      },
      default: 'closed'
    })).to.throw(/^Unknown state transition:/);
  });

  it('provides a getter to query states', function () {
    expect(this.gate.states).to.include.members(['open', 'closed']);
  });

  it('honors default state', function () {
    expect(this.gate.is('closed')).to.be.true;
  });

  describe('#is(states[, fn[, context]])', function () {
    it('returns `false` if not at state', function () {
      expect(this.gate.is('open')).to.be.false;
    });

    it('invokes callback if at state', function () {
      var spy = sinon.spy();
      this.gate.is('closed', spy);
      expect(spy).to.have.been.called;
    });

    it('returns `true` if at state', function () {
      expect(this.gate.is('closed')).to.be.true;
    });

    it('accepts a single state or an array of states', function () {
      expect(this.gate.is(['open', 'closed'])).to.be.true;
    });
  });

  describe('#on(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.gate, 'is');
      this.gate.on('closed', sinon.spy());
      expect(is).to.have.been.calledWith('closed');
    });

    it('enqueues callback, invoking when transitioned to state', function () {
      var gate = this.gate;
      var spy = sinon.spy();

      gate.on('closed', spy);
      ['open', 'closed'].forEach(function (state) {
        gate.to(state);
      });
      expect(spy).to.have.been.calledTwice;
    });
  });

  describe('#once(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.gate, 'is');
      this.gate.once('closed', this.sinon.spy());
      expect(is).to.have.been.calledWith('closed');
    });

    it('enqueues callback if not invoked, doing so just once when transitioned to state', function () {
      var gate = this.gate;
      var spy = sinon.spy();

      gate.once('open', spy);
      ['open', 'closed', 'open'].forEach(function (state) {
        gate.to(state);
      });
      expect(spy).to.have.been.calledOnce;
    });
  });

  describe('#to(state[, ...args])', function () {
    it('transitions to given state, invoking callbacks', function (done) {
      this.gate.on('open', done);
      this.gate.to('open');
    });

    it('does not allow transitioning to invalid state', function () {
      var gate = this.gate;

      expect(function () {
        gate.to('closed');
      }).to.throw(/^Invalid transition:/);
    });

    it('invokes callbacks with passed arguments', function () {
      var args = {my: 'arguments'};
      var spy = sinon.spy();

      this.gate.on('open', spy);
      this.gate.to('open', args);
      expect(spy).to.have.been.calledWithExactly(args);
    });
  });

  describe('#when(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.gate, 'is');
      this.gate.when('closed', this.sinon.spy());
      expect(is).to.have.been.calledWith('closed');
    });

    it('enqueues callback if not invoked, doing so only if transitioned to next', function () {
      var gate = this.gate;
      var spy = sinon.spy();

      gate.when('open', spy);
      ['open', 'closed', 'open'].forEach(function (state) {
        gate.to(state);
      });
      expect(spy).to.have.been.calledOnce;
    });

    it('enqueues callback if not invoked, discarding it if not next transition', function () {
      var intensity = engineer({
        states: {
          high: ['medium'],
          medium: ['high', 'low'],
          low: ['medium']
        },
        default: 'medium'
      });
      var spy = sinon.spy();

      intensity.when('high', spy);
      ['low', 'medium', 'high'].forEach(function (state) {
        intensity.to(state);
      });
      expect(spy).to.not.have.been.called;
    });
  });
});
<commit_msg>Used a new state machine for tests<commit_after>/*jshint mocha: true, -W030 */
var chai = require('chai');
var engineer = require('../lib/machine');
var sinon = require('sinon');

chai.use(require('sinon-chai'));

// Reference chai's `expect()``
var expect = chai.expect;

// Returns a function that creates a state machine
var apply = function (options) {
  return function () {
    engineer(options);
  };
};

describe('engineer(options)', function () {
  beforeEach(function () {
    this.sinon = sinon.sandbox.create();
  });

  beforeEach(function () {
    this.intensity = engineer({
      states: {
        high: ['medium'],
        medium: ['low', 'high'],
        low: ['rest', 'medium'],
        rest: ['low']
      },
      default: 'rest'
    });
  });

  afterEach(function () {
    this.sinon.restore();
  });

  it('throws if not passed options', function () {
    expect(apply()).to.throw('Expected options');
  });

  it('throws if no state transitions are specified', function () {
    expect(apply({default: 'open'})).to.throw('Expected map of state transitions');
  });

  it('throws if no default state is specified', function () {
    expect(apply({
      states: {
        closed: ['open'],
        open: ['closed']
      }
    })).to.throw('Expected default state');
  });

  it('throws if default state is unknown', function () {
    expect(apply({
      states: {
        closed: ['open'],
        open: ['closed']
      },
      default: 'half-open'
    })).to.throw(/^Invalid default state:/);
  });

  it('throws if state transitions to unknown state', function () {
    expect(apply({
      states: {
        closed: ['open', 'half-open'],
        open: ['closed']
      },
      default: 'closed'
    })).to.throw(/^Unknown state transition:/);
  });

  it('provides a getter to query states', function () {
    expect(this.intensity.states).to.include.members(['rest', 'low', 'medium', 'high']);
  });

  it('honors default state', function () {
    expect(this.intensity.is('rest')).to.be.true;
  });

  describe('#is(states[, fn[, context]])', function () {
    it('returns `false` if not at state', function () {
      expect(this.intensity.is('high')).to.be.false;
    });

    it('invokes callback if at state', function () {
      var spy = sinon.spy();
      this.intensity.is('rest', spy);
      expect(spy).to.have.been.called;
    });

    it('returns `true` if at state', function () {
      expect(this.intensity.is('rest')).to.be.true;
    });

    it('accepts a single state or an array of states', function () {
      expect(this.intensity.is(['rest', 'low'])).to.be.true;
    });
  });

  describe('#on(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.intensity, 'is');
      this.intensity.on('rest', sinon.spy());
      expect(is).to.have.been.calledWith('rest');
    });

    it('enqueues callback, invoking when transitioned to state', function () {
      var intensity = this.intensity;
      var spy = sinon.spy();

      intensity.on('rest', spy);
      ['low', 'rest'].forEach(function (state) {
        intensity.to(state);
      });
      expect(spy).to.have.been.calledTwice;
    });
  });

  describe('#once(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.intensity, 'is');
      this.intensity.once('rest', this.sinon.spy());
      expect(is).to.have.been.calledWith('rest');
    });

    it('enqueues callback if not invoked, doing so just once when transitioned to state', function () {
      var intensity = this.intensity;
      var spy = sinon.spy();

      intensity.once('low', spy);
      ['low', 'rest', 'low'].forEach(function (state) {
        intensity.to(state);
      });
      expect(spy).to.have.been.calledOnce;
    });
  });

  describe('#to(state[, ...args])', function () {
    it('transitions to given state, invoking callbacks', function (done) {
      this.intensity.on('low', done);
      this.intensity.to('low');
    });

    it('does not allow transitioning to invalid state', function () {
      var intensity = this.intensity;

      expect(function () {
        intensity.to('closed');
      }).to.throw(/^Invalid transition:/);
    });

    it('invokes callbacks with passed arguments', function () {
      var args = {my: 'arguments'};
      var spy = sinon.spy();

      this.intensity.on('low', spy);
      this.intensity.to('low', args);
      expect(spy).to.have.been.calledWithExactly(args);
    });
  });

  describe('#when(state, fn[, context])', function () {
    it('determines whether to invoke callback immediately', function () {
      var is = this.sinon.spy(this.intensity, 'is');
      this.intensity.when('rest', this.sinon.spy());
      expect(is).to.have.been.calledWith('rest');
    });

    it('enqueues callback if not invoked, doing so only if transitioned to next', function () {
      var intensity = this.intensity;
      var spy = sinon.spy();

      intensity.when('low', spy);
      ['low', 'rest', 'low'].forEach(function (state) {
        intensity.to(state);
      });
      expect(spy).to.have.been.calledOnce;
    });

    it('enqueues callback if not invoked, discarding it if not next transition', function () {
      var intensity = this.intensity;
      var spy = sinon.spy();

      intensity.when('high', spy);
      ['low', 'medium', 'high'].forEach(function (state) {
        intensity.to(state);
      });
      expect(spy).to.not.have.been.called;
    });
  });
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function (aGlobal) {
  Components.utils.import('resource://gre/modules/Services.jsm');

  var NoDelete = {
    getTargetFolder: function(aFolder) {
      var folders = aFolder ? [aFolder] :
                    gFolderTreeView ? gFolderTreeView.getSelectedFolders() : // messenger.xul
                    gFolderDisplay.displayedFolder ; // messageWindow.xul
      return folders[0];
    },

    isEmpty: function(aFolder) {
      return !aFolder.hasSubFolders && aFolder.getTotalMessages(false) === 0;
    },

    isInTrash: function(aFolder) {
      if (!aFolder)
        return false;

      if (aFolder.flags & Ci.nsMsgFolderFlags.Trash)
        return true;

      return this.isInTrash(aFolder.parent);
    },

    isCommandDisabled: function(aCommand) {
      switch (aCommand) {
        case 'cmd_shiftDelete':
        case 'button_shiftDelete':
          return true;

        case 'cmd_delete':
        case 'button_delete':
          return this.isInTrash(this.getTargetFolder());
      }

      return false;
    },

    init: function() {
      window.__no_delete__goDoCommand = window.goDoCommand;
      window.goDoCommand = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return;
        return window.__no_delete__goDoCommand.call(this, aCommand);
      };

      if ('FolderPaneController' in window) {
        FolderPaneController.__no_delete__supportsCommand = FolderPaneController.supportsCommand;
        FolderPaneController.supportsCommand = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return false;
          return FolderPaneController.__no_delete__supportsCommand.call(this, aCommand);
      };

        FolderPaneController.__no_delete__isCommandEnabled = FolderPaneController.isCommandEnabled;
        FolderPaneController.isCommandEnabled = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return false;
          return FolderPaneController.__no_delete__isCommandEnabled.call(this, aCommand);
      };
      }

      if ('DefaultController' in window) {
        DefaultController.__no_delete__supportsCommand = DefaultController.supportsCommand;
        DefaultController.supportsCommand = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return false;
          return DefaultController.__no_delete__supportsCommand.call(this, aCommand);
      };

        DefaultController.__no_delete__isCommandEnabled = DefaultController.isCommandEnabled;
        DefaultController.isCommandEnabled = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return false;
          return DefaultController.__no_delete__isCommandEnabled.call(this, aCommand);
      };

        DefaultController.__no_delete__doCommand = DefaultController.doCommand;
        DefaultController.doCommand = function(aCommand, aTab) {
        if (NoDelete.isCommandDisabled(aCommand))
          return false;
          return DefaultController.__no_delete__doCommand.call(this, aCommand, aTab);
      };
      }
    }
  };

  document.addEventListener('DOMContentLoaded', function onDOMContentLoaded(aEvent) {
    document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
    NoDelete.init();
  });

  aGlobal.NoDelete = NoDelete;
})(this);
<commit_msg>Fix indent<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function (aGlobal) {
  Components.utils.import('resource://gre/modules/Services.jsm');

  var NoDelete = {
    getTargetFolder: function(aFolder) {
      var folders = aFolder ? [aFolder] :
                    gFolderTreeView ? gFolderTreeView.getSelectedFolders() : // messenger.xul
                    gFolderDisplay.displayedFolder ; // messageWindow.xul
      return folders[0];
    },

    isEmpty: function(aFolder) {
      return !aFolder.hasSubFolders && aFolder.getTotalMessages(false) === 0;
    },

    isInTrash: function(aFolder) {
      if (!aFolder)
        return false;

      if (aFolder.flags & Ci.nsMsgFolderFlags.Trash)
        return true;

      return this.isInTrash(aFolder.parent);
    },

    isCommandDisabled: function(aCommand) {
      switch (aCommand) {
        case 'cmd_shiftDelete':
        case 'button_shiftDelete':
          return true;

        case 'cmd_delete':
        case 'button_delete':
          return this.isInTrash(this.getTargetFolder());
      }

      return false;
    },

    init: function() {
      window.__no_delete__goDoCommand = window.goDoCommand;
      window.goDoCommand = function(aCommand) {
        if (NoDelete.isCommandDisabled(aCommand))
          return;
        return window.__no_delete__goDoCommand.call(this, aCommand);
      };

      if ('FolderPaneController' in window) {
        FolderPaneController.__no_delete__supportsCommand = FolderPaneController.supportsCommand;
          FolderPaneController.supportsCommand = function(aCommand) {
          if (NoDelete.isCommandDisabled(aCommand))
            return false;
          return FolderPaneController.__no_delete__supportsCommand.call(this, aCommand);
        };

        FolderPaneController.__no_delete__isCommandEnabled = FolderPaneController.isCommandEnabled;
        FolderPaneController.isCommandEnabled = function(aCommand) {
          if (NoDelete.isCommandDisabled(aCommand))
            return false;
          return FolderPaneController.__no_delete__isCommandEnabled.call(this, aCommand);
        };
      }

      if ('DefaultController' in window) {
        DefaultController.__no_delete__supportsCommand = DefaultController.supportsCommand;
        DefaultController.supportsCommand = function(aCommand) {
          if (NoDelete.isCommandDisabled(aCommand))
            return false;
          return DefaultController.__no_delete__supportsCommand.call(this, aCommand);
        };

        DefaultController.__no_delete__isCommandEnabled = DefaultController.isCommandEnabled;
        DefaultController.isCommandEnabled = function(aCommand) {
          if (NoDelete.isCommandDisabled(aCommand))
            return false;
          return DefaultController.__no_delete__isCommandEnabled.call(this, aCommand);
        };

        DefaultController.__no_delete__doCommand = DefaultController.doCommand;
        DefaultController.doCommand = function(aCommand, aTab) {
          if (NoDelete.isCommandDisabled(aCommand))
            return false;
          return DefaultController.__no_delete__doCommand.call(this, aCommand, aTab);
        };
      }
    }
  };

  document.addEventListener('DOMContentLoaded', function onDOMContentLoaded(aEvent) {
    document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
    NoDelete.init();
  });

  aGlobal.NoDelete = NoDelete;
})(this);
<|endoftext|>