<commit_before>var compact = require('./compact')
var createSST = require('./sst')
var mem = require('./mem')
var zeros = '00000000'
function pad (n) {
  n = n.toString()
  return zeros.substring(0, n.length) + n
}

module.exports = function (createSST, createMemtable, createManifest) {

  return function (location, opts) {
    var memtable = mem(), counter = 0, db, compacting = false, _snapshot
    var tables = tables || [memtable]
    var manifest, tables, seq
    return db = {
      //should i separate all this stuff out so that
      //you can simulate the database all in memory?
      open: function (opts, cb) {
        if(!cb) cb = opts, opts = {}
        mkdirp(location, function (err) {
          if(err) return cb(err)
          manifest = creatManifest(path.join(location, 'manifest.json'))
          manifest.open(function (err) {
            if(err) return cb(err)
            //if the manifest is empty create an empty memory table.
            if(!manifest.data) {
              seq = 0
              var filename =
              var _memtable = createMemtable(path.join(location, 'log-'+pad(seq)+'.json'))
              manifest.update({tables: ['log-'+pad(seq)+'.json'], seq: seq}, function (err) {
                if(err) return cb(err)
                _memtable.open(function (err) {
                  if(err) cb(err)
                  memtable = _memtable
                  _snapshot = [memtable]
                  cb(null, db)
                })
              })
            } else {
              //open all the tables.
              var _tables = []
              var n = manifest.tables.length
              manifest.tables.forEach(function (name, i) {
                var m = /^(log|sst)-(\d+)\.json$/.exec(name)
                var type = m[1], _seq = m[2], table
                var create = type == 'log' ? createMemtable : createSST
                var table = tables[i] = create(name)
                table.open(next)
              })
              function next (err) {
                if(err) return n = -1, cb(err)
                if(--n) return
                cb(null, db)
              }
            }
          })
        }
      },
    //get the current snapshot.
      snapshot: function () {
        if(_snapshot) {
          tables = _snapshot; _snapshot = null
        }
        return tables
      },
      //step through all the databases, and look for next
      get: function (key, cb) {
        var tables = db.snapshot()
        ;(function next (i) {
          //if we ran out of tables, err
          //gets need to use snapshots too...
          //but since they will return from the memtable
          //synchronously they will only need to track the SSTs
          //which probably means they can all share a snapshot
          //unless there is a compaction happening.

          //just have an SST snapshot,
          //keep a count of current gets
          //and don't delete any ssts until it's freed.

          if(!tables[i]) return cb(new Error('not found'))
          tables[i].get(key, function (err, value) {
            if(err) return next(i)
            return cb(null, value)
          })
        })(0)
      },
      //only write to the FIRST table.
      put: function (key, value, cb) {
        if(Math.random() < 0.1) console.log(counter)
        return memtable.put(key, value, function (err) {
          if(!(++counter % 200))
            db.compact()
          //db.compact()
          //maybe compact?
          //just check if the number of records added
          cb(err)
        })
      },
      createReadStream: function (opts) {
        //merge streams from all the tables
        //it's probably okay to not have a snapshot on the memtable.
        //hmm, on the other hand, it will be fairly cheap...
        //otherwise I'd need to implement a skiplist, so you can have
        //streams that handle inserts.

        //TRACK HOW MANY ITERATORS ARE USING THIS SNAPSHOT.

        //read snapshots on the memtable are very simple, because it just
        //figures out the range sync and copies it to an array
        //so that doesn't need to be tracked specially.

        var tables = db.snapshot()
        var stream = tables[0].createReadStream(opts)
        for(var i = 1; i < tables.length; i++)
          stream = merge(tables[i].createReadStream(opts), stream)

        return stream
      },
      compact: function (cb) {
        console.log('COMPACT')
        //make a temp snapshot for use while compacting.
        //only one compaction at a time.
        if(compacting) return
        compacting = true
        // create a new memtable, to save any writes while we are compacting.
        // save it in the manifest FIRST. That way we can be sure that we don't
        // loose that data if we crash while compacting.
//        manifest.put('tables',

        //add a new memtable to the manifest
        var name = nextTamleName('log')
        var tables = db.snapshot()
        var names = tables.map(function (e) {
          return path.basename(e.location)
        })
        names.unshift(name)
        manifest.update({tables: names, seq: seq}, function (err) {

          _snapshot = [memtable].concat(tables)
          var table = createMemtable(path.join(location, name).open(function ()

          console.log('COMPACTING...')
          memtable =  mem(nextTableName('log'))
          compact(tables, createSST, function (err, sst, tables) {
          console.log('COMPACTING...DONE')
            compacting = false
            //now that we have generated the sst,
            //save the new table set in the manifest,
            //so that if the process crashes we will reload the right data.
            //hmm, maybe we could just start using it... sst table are immutable, anyway.
            //the most important thing to have right is the memtables.
            //when an sst is no longer used (been compacted + no more iterators)
            //then it's safe to delete.
            _snapshot = [memtable, sst].concat(tables)
            if(cb) cb()
          })
        })
      }
    }
  }
}
<commit_msg>refactor to inject actual implementations<commit_after>var compact = require('./compact')
var createSST = require('./sst')
var mem = require('./mem')
var mkdirp = require('mkdirp')
var path = require('path')

var zeros = '00000000'

function pad (n) {
  n = n.toString()
  return zeros.substring(0, n.length) + n
}

function isEmpty (o) {
  if(null == o) return true
  for(var k in o)
    return false
  return true
}

module.exports = function (createSST, createMemtable, createManifest) {

  return function (location, opts) {
    var memtable = mem(), counter = 0, db, compacting = false, _snapshot
    var tables = tables || [memtable]
    var manifest, tables, seq

    function nextTableName(type) {
      return type + '-' + pad(seq++) + '.json'
    }
    return db = {
      //should i separate all this stuff out so that
      //you can simulate the database all in memory?
      open: function (opts, cb) {
        if(!cb) cb = opts, opts = {}
        mkdirp(location, function (err) {
          if(err) return cb(err)
          manifest = createManifest(path.join(location, 'manifest.json'))
          manifest.open(function (err) {
            if(err) return cb(err)
            console.log('open')
            //if the manifest is empty create an empty memory table.
            if(isEmpty(manifest.data)) {
              seq = 0
              var filename = path.join(location, 'log-'+pad(seq)+'.json')
              var _memtable = createMemtable(filename)
              manifest.update({tables: ['log-'+pad(seq)+'.json'], seq: seq}, function (err) {
                if(err) return cb(err)
                console.log('updated')
                _memtable.open(function (err) {
                  console.log('memtable open')
                  if(err) cb(err)
                  memtable = _memtable
                  _snapshot = [memtable]
                  console.log(_snapshot)
                cb(null, db)
                })
              })
            } else {
              //open all the tables.
              var _tables = manifest.data.tables || []
              var n = tables.length
              _tables.forEach(function (name, i) {
                console.log(name, i)
                var m = /^(log|sst)-(\d+)\.json$/.exec(name)
                var type = m[1], _seq = m[2], table
                var create = type == 'log' ? createMemtable : createSST
                var table = tables[i] = create(name)
                console.log('table', i, type, name)
                table.open(next)
              })
              function next (err) {
                if(err) return n = -1, cb(err)
                if(--n) return
                cb(null, db)
              }
            }
          })
        })
      },
    //get the current snapshot.
      nextSnapshot: function (ary) {
        _snapshot = ary
        return db
      },
      snapshot: function () {
        if(_snapshot) {
          tables = _snapshot; _snapshot = null
        }
        return tables
      },
      //step through all the databases, and look for next
      get: function (key, cb) {
        var tables = db.snapshot()
        ;(function next (i) {
          //if we ran out of tables, err
          //gets need to use snapshots too...
          //but since they will return from the memtable
          //synchronously they will only need to track the SSTs
          //which probably means they can all share a snapshot
          //unless there is a compaction happening.

          //just have an SST snapshot,
          //keep a count of current gets
          //and don't delete any ssts until it's freed.

          if(!tables[i]) return cb(new Error('not found'))
          tables[i].get(key, function (err, value) {
            if(err) return next(i)
            return cb(null, value)
          })
        })(0)
      },
      //only write to the FIRST table.
      put: function (key, value, cb) {
        if(Math.random() < 0.1) console.log(counter)
        return memtable.put(key, value, function (err) {
          if(!(++counter % 200))
            db.compact()
          //db.compact()
          //maybe compact?
          //just check if the number of records added
          cb(err)
        })
      },
      createReadStream: function (opts) {
        //merge streams from all the tables
        //it's probably okay to not have a snapshot on the memtable.
        //hmm, on the other hand, it will be fairly cheap...
        //otherwise I'd need to implement a skiplist, so you can have
        //streams that handle inserts.

        //TRACK HOW MANY ITERATORS ARE USING THIS SNAPSHOT.

        //read snapshots on the memtable are very simple, because it just
        //figures out the range sync and copies it to an array
        //so that doesn't need to be tracked specially.

        var tables = db.snapshot()

        var stream = tables[0].createReadStream(opts)
        for(var i = 1; i < tables.length; i++)
          stream = merge(tables[i].createReadStream(opts), stream)

        return stream
      },
      compact: function (cb) {
        console.log('COMPACT')
        //make a temp snapshot for use while compacting.
        //only one compaction at a time.
        if(compacting) return
        compacting = true
        // create a new memtable, to save any writes while we are compacting.
        // save it in the manifest FIRST. That way we can be sure that we don't
        // loose that data if we crash while compacting.
//        manifest.put('tables',

        //add a new memtable to the manifest
        var name = nextTableName('log')
        var tables = db.snapshot()

        var names = tables.map(function (e) {
          return path.basename(e.location)

        })
        names.unshift(name)
        manifest.update({tables: names, seq: seq}, function (err) {

          var _memtable = createMemtable(path.join(location, name))
          _memtable.open(function (err) {
            if(err) return cb(err)
            memtable = _memtable
            db.nextSnapshot([memtable].concat(tables))
            console.log('COMPACTING...')

            //maybe the api for sst should have a write stream,
            //except it errors unles it's all in order,
            //and you can only use it once?
            tables = compact(tables, createSST(location, function (err, sst) {
              if(err) return cb(err)
              console.log('COMPACTING...DONE')
              compacting = false
              //now that we have generated the sst,
              //save the new table set in the manifest,
              //so that if the process crashes we will reload the right data.
              //hmm, maybe we could just start using it... sst table are immutable, anyway.
              //the most important thing to have right is the memtables.
              //when an sst is no longer used (been compacted + no more iterators)
              //then it's safe to delete.
              db.nextSnapshot([memtable, sst].concat(tables))
              if(cb) cb()
            }))
          })
        })
      }
    }
  }
}
<|endoftext|>


<commit_before>/*
 * grunt-build-gh-pages
 * https://github.com/pajtai/grunt-build-gh-pages
 *
 * Copyright (c) 2013 Peter Ajtai
 * Licensed under the MIT license.
 */

'use strict';

// TODO: use something Thor like to manage all these shell tasks
module.exports = function(grunt) {

    var path = require('path'),
        _ = require('underscore');

    // Make sure grunt-dependecies are loaded
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-clean');

    // Please see the Grunt documentation for more information regarding task
    // creation: http://gruntjs.com/creating-tasks

    grunt.registerMultiTask('build_gh_pages', 'Take a build from this branch to another branch.', function() {

        // this.name
        // this.target
        // Merge task-specific and/or target-specific options with these defaults.
        // things coming through options are automatically run through the grunt templater
        var options = this.options({
                dist: "dist",
                build_branch: "gh-pages",
                pull: true,
                exclude: [],
                copy_hidden: true
            }),
            shell = "shell",
            prefix = "build_gh_pages_",

        // Get the SHA reference of the current commit
            getRef = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        var version = grunt.file.readJSON("package.json").version,
                            shaRef =  out.substring(0, 7);

                        grunt.log.writeln("sha ref is: " + shaRef)
                            .writeln("version is: " + version);

                        grunt.config.set(prefix + ".shaRef", shaRef);
                        grunt.config.set(prefix + ".version", version);

                        cb();
                    }
                },

                command: "git rev-parse HEAD"
            },

        // Get the name of the current branch
            getBranch = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        grunt.log.writeln("branch is: " + out);
                        grunt.config.set(prefix + ".branch", out);
                        cb();
                    }
                },

                command: "git rev-parse --abbrev-ref HEAD"
            },

        // Get the name of the current branch
            verifyBranch = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        grunt.log.writeln("switched to branch " + out);
                        if (out.replace(/^\s+|\s+$/g, '') !== options.build_branch.replace(/^\s+|\s+$/g, '')) {
                            grunt.fatal("Stop. Branch, " + options.build_branch + " does not exist. Please create an --orphan branch of that name, and try again.", grunt.fail.code.TASK_FAILURE);
                        }
                        cb();
                    }
                },

                command: "git rev-parse --abbrev-ref HEAD"
            },

        // Switch to the build branch and make sure it is up to date if desired
            switchBranch = {
                options: {
                    stderr: true,
                    stdout: true
                },

                command: (function() {
                    var command = ["git checkout " + options.build_branch];

                    if (options.pull) {
                        command.push("git pull --rebase origin " + options.build_branch);
                    }
                    return command.join('&&');
                }())
            },

            copy = {
                files: [
                    {
                        expand: true,
                        cwd: options.dist + "/",
                        src: ['**'],
                        dest: process.cwd(),
                        dot: options.copy_hidden
                    }
                ]
            },

            cleanDist = {
                files: [{src: [options.dist]}]
            },

            cleanProj = {
                options: {
                    force: true
                },
                files: [{
                    force: true,
                    src: buildCleanProjFileList()
                }]
            },

            commit = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command : [
                    'git add -A',
                    generateGitCommitCommand()
                ].join(' && ')
            },

            push = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command : 'git push origin ' + options.build_branch
            },

            checkout = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command :'git checkout <%= grunt.config.get("build_gh_pages_.branch") %>'
            };

        grunt.log
            .subhead("build gh pages options:")
            .writeln(JSON.stringify(options));

        // dynamically create desired shell tasks
        grunt.config.set("shell." + prefix + "getRef", getRef);
        grunt.config.set("shell." + prefix + "getBranch", getBranch);
        grunt.config.set("shell." + prefix + "switchBranch", switchBranch);
        grunt.config.set("shell." + prefix + "verifyBranch", verifyBranch);
        grunt.config.set("clean." + prefix + "cleanProj", cleanProj);
        grunt.config.set("clean." + prefix + "cleanDist", cleanDist);
        grunt.config.set("copy." + prefix + "copy", copy);
        grunt.config.set("shell." + prefix + "commit", commit);
        grunt.config.set("shell." + prefix + "push", push);
        grunt.config.set("shell." + prefix + "checkout", checkout);

        grunt.registerTask(prefix + "bumpBuild", function () {
            var build = ".build";
            grunt.file.write(build, path.existsSync(build) ? parseInt(grunt.file.read(build), 10) + 1 : 1);
        });

        // run created shell tasks
        grunt.task.run([
            "shell:" + prefix + "getRef",
            "shell:" + prefix + "getBranch",
            "shell:" + prefix + "switchBranch",
            "shell:" + prefix + "verifyBranch",
            prefix + "bumpBuild",
            "clean:" + prefix + "cleanProj",
            "copy:" + prefix + "copy",
            "clean:" + prefix + "cleanDist",
            "shell:" + prefix + "commit",
            "shell:" + prefix + "push",
            "shell:" + prefix + "checkout"
        ]);
    });

    // commit all files using the version number as the commit message
    // <%= %> is grunt templating
    function generateGitCommitCommand() {
        return 'git commit -am "' +
            'Build: <%= grunt.file.read(".build") %> ' +
            'Branch: <%= grunt.config.get("build_gh_pages_.branch") %> <%= grunt.config.get("build_gh_pages_.version") %> ' +
            'SHA: <%= grunt.config.get("build_gh_pages_.shaRef") %>"';
    }

    function buildCleanProjFileList() {
        var arr = ['./**', '!.', '!..', '!./.git/**', '!./node_modules/**', '!./' + options.dist + "/**"];
        _.each(options.exclude, function(item){
            arr.push('!' + item);
        });
        return arr;
    }
};
<commit_msg>Fixed bug with options not being available outside of the tasks scope<commit_after>/*
 * grunt-build-gh-pages
 * https://github.com/pajtai/grunt-build-gh-pages
 *
 * Copyright (c) 2013 Peter Ajtai
 * Licensed under the MIT license.
 */

'use strict';

// TODO: use something Thor like to manage all these shell tasks
module.exports = function(grunt) {

    var path = require('path'),
        _ = require('underscore');

    // Make sure grunt-dependecies are loaded
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-clean');

    // Please see the Grunt documentation for more information regarding task
    // creation: http://gruntjs.com/creating-tasks

    grunt.registerMultiTask('build_gh_pages', 'Take a build from this branch to another branch.', function() {

        // this.name
        // this.target
        // Merge task-specific and/or target-specific options with these defaults.
        // things coming through options are automatically run through the grunt templater
        var options = this.options({
                dist: "dist",
                build_branch: "gh-pages",
                pull: true,
                exclude: [],
                copy_hidden: true
            }),
            shell = "shell",
            prefix = "build_gh_pages_",

        // Get the SHA reference of the current commit
            getRef = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        var version = grunt.file.readJSON("package.json").version,
                            shaRef =  out.substring(0, 7);

                        grunt.log.writeln("sha ref is: " + shaRef)
                            .writeln("version is: " + version);

                        grunt.config.set(prefix + ".shaRef", shaRef);
                        grunt.config.set(prefix + ".version", version);

                        cb();
                    }
                },

                command: "git rev-parse HEAD"
            },

        // Get the name of the current branch
            getBranch = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        grunt.log.writeln("branch is: " + out);
                        grunt.config.set(prefix + ".branch", out);
                        cb();
                    }
                },

                command: "git rev-parse --abbrev-ref HEAD"
            },

        // Get the name of the current branch
            verifyBranch = {
                options: {
                    callback: function(err, out, stderr, cb) {
                        grunt.log.writeln("switched to branch " + out);
                        if (out.replace(/^\s+|\s+$/g, '') !== options.build_branch.replace(/^\s+|\s+$/g, '')) {
                            grunt.fatal("Stop. Branch, " + options.build_branch + " does not exist. Please create an --orphan branch of that name, and try again.", grunt.fail.code.TASK_FAILURE);
                        }
                        cb();
                    }
                },

                command: "git rev-parse --abbrev-ref HEAD"
            },

        // Switch to the build branch and make sure it is up to date if desired
            switchBranch = {
                options: {
                    stderr: true,
                    stdout: true
                },

                command: (function() {
                    var command = ["git checkout " + options.build_branch];

                    if (options.pull) {
                        command.push("git pull --rebase origin " + options.build_branch);
                    }
                    return command.join('&&');
                }())
            },

            copy = {
                files: [
                    {
                        expand: true,
                        cwd: options.dist + "/",
                        src: ['**'],
                        dest: process.cwd(),
                        dot: options.copy_hidden
                    }
                ]
            },

            cleanDist = {
                files: [{src: [options.dist]}]
            },

            cleanProj = {
                options: {
                    force: true
                },
                files: [{
                    force: true,
                    src: buildCleanProjFileList(options.dist, options.exclude)
                }]
            },

            commit = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command : [
                    'git add -A',
                    generateGitCommitCommand()
                ].join(' && ')
            },

            push = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command : 'git push origin ' + options.build_branch
            },

            checkout = {
                options : {
                    stderr : true,
                    stdout : true
                },
                command :'git checkout <%= grunt.config.get("build_gh_pages_.branch") %>'
            };

        grunt.log
            .subhead("build gh pages options:")
            .writeln(JSON.stringify(options));

        // dynamically create desired shell tasks
        grunt.config.set("shell." + prefix + "getRef", getRef);
        grunt.config.set("shell." + prefix + "getBranch", getBranch);
        grunt.config.set("shell." + prefix + "switchBranch", switchBranch);
        grunt.config.set("shell." + prefix + "verifyBranch", verifyBranch);
        grunt.config.set("clean." + prefix + "cleanProj", cleanProj);
        grunt.config.set("clean." + prefix + "cleanDist", cleanDist);
        grunt.config.set("copy." + prefix + "copy", copy);
        grunt.config.set("shell." + prefix + "commit", commit);
        grunt.config.set("shell." + prefix + "push", push);
        grunt.config.set("shell." + prefix + "checkout", checkout);

        grunt.registerTask(prefix + "bumpBuild", function () {
            var build = ".build";
            grunt.file.write(build, path.existsSync(build) ? parseInt(grunt.file.read(build), 10) + 1 : 1);
        });

        // run created shell tasks
        grunt.task.run([
            "shell:" + prefix + "getRef",
            "shell:" + prefix + "getBranch",
            "shell:" + prefix + "switchBranch",
            "shell:" + prefix + "verifyBranch",
            prefix + "bumpBuild",
            "clean:" + prefix + "cleanProj",
            "copy:" + prefix + "copy",
            "clean:" + prefix + "cleanDist",
            "shell:" + prefix + "commit",
            "shell:" + prefix + "push",
            "shell:" + prefix + "checkout"
        ]);
    });

    // commit all files using the version number as the commit message
    // <%= %> is grunt templating
    function generateGitCommitCommand() {
        return 'git commit -am "' +
            'Build: <%= grunt.file.read(".build") %> ' +
            'Branch: <%= grunt.config.get("build_gh_pages_.branch") %> <%= grunt.config.get("build_gh_pages_.version") %> ' +
            'SHA: <%= grunt.config.get("build_gh_pages_.shaRef") %>"';
    }

    function buildCleanProjFileList(target, exclusions) {
        var arr = ['./**', '!.', '!..', '!./.git/**', '!./node_modules/**', '!./' + target + "/**"];
        _.each(exclusions, function(item){
            arr.push('!' + item);
        });
        return arr;
    }
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Unit tests for TypeHierarchy.
 */

const chai = require('chai');

const {TypeHierarchy} = require('../src/type_hierarchy');

suite('TypeHierarchy', function() {
  test('Super not defined', function() {
    chai.assert.throws(
        function() {
          new TypeHierarchy({
            'typeA': {
              'fulfills': ['typeB'],
            },
          });
        },
        'The type typea says it fulfills the type typeb, but that type is not' +
        ' defined');
  });

  suite('typeExists', function() {
    test('Simple', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeExists('typeA'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeExists('typea'),
          'Expected TypeHierarchy to be case-insensitive.');
    });

    test('Padding', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeExists(' typeA '),
          'Expected TypeHierarchy to respect padding.');
    });
  });

  suite('typeIsExactlyType', function() {
    test('Simple', function() {
      const hierarchy = new TypeHierarchy({
        'A': { },
      });
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typeA', 'typeA'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typeA', 'typea'),
          'Expected TypeHierarchy to be case-insensitive.');
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typea', 'typeA'),
          'Expected TypeHierarchy to be case-insensitive.');
    });

    test('Padding', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', ' typeA '),
          'Expected TypeHierarchy to respect padding.');
    });

    test('Super', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeB': { },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', 'typeB'));
    });

    test('Sub', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
        'typeB': {
          'fulfills': ['typeA'],
        },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', 'typeB'));
    });
  });

  suite('typeFulfillsType', function() {
    test('Empty fulfills', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': [],
        },
      });
      chai.assert.isFalse(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Undefined fulfills', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Super defined first', function() {
      const hierarchy = new TypeHierarchy({
        'typeB': { },
        'typeA': {
          'fulfills': ['typeB'],
        },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Super defined second', function() {
      const hierarchy = new TypeHierarchy({
        'A': {
          'fulfills': ['B'],
        },
        'B': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('A', 'B'));
    });

    test('Multiple supers', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB', 'typeC', 'typeD'],
        },
        'typeB': { },
        'typeC': { },
        'typeD': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeC'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeD'));
    });

    test('Deep super', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeB': {
          'fulfills': ['typeC'],
        },
        'typeC': {
          'fulfills': ['typeD'],
        },
        'typeD': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeC'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeD'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeb': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeb'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typea', 'typeb'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typea', 'typeB'));
    });
  });

  suite('')
});
<commit_msg>Add tests for nearest common parents<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Unit tests for TypeHierarchy.
 */

const chai = require('chai');

const {TypeHierarchy} = require('../src/type_hierarchy');

suite('TypeHierarchy', function() {
  test('Super not defined', function() {
    chai.assert.throws(
        function() {
          new TypeHierarchy({
            'typeA': {
              'fulfills': ['typeB'],
            },
          });
        },
        'The type typea says it fulfills the type typeb, but that type is not' +
        ' defined');
  });

  suite('typeExists', function() {
    test('Simple', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeExists('typeA'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeExists('typea'),
          'Expected TypeHierarchy to be case-insensitive.');
    });

    test('Padding', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeExists(' typeA '),
          'Expected TypeHierarchy to respect padding.');
    });
  });

  suite('typeIsExactlyType', function() {
    test('Simple', function() {
      const hierarchy = new TypeHierarchy({
        'A': { },
      });
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typeA', 'typeA'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typeA', 'typea'),
          'Expected TypeHierarchy to be case-insensitive.');
      chai.assert.isTrue(hierarchy.typeIsExactlyType('typea', 'typeA'),
          'Expected TypeHierarchy to be case-insensitive.');
    });

    test('Padding', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', ' typeA '),
          'Expected TypeHierarchy to respect padding.');
    });

    test('Super', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeB': { },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', 'typeB'));
    });

    test('Sub', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
        'typeB': {
          'fulfills': ['typeA'],
        },
      });
      chai.assert.isFalse(hierarchy.typeIsExactlyType('typeA', 'typeB'));
    });
  });

  suite('typeFulfillsType', function() {
    test('Empty fulfills', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': [],
        },
      });
      chai.assert.isFalse(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Undefined fulfills', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': { },
      });
      chai.assert.isFalse(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Super defined first', function() {
      const hierarchy = new TypeHierarchy({
        'typeB': { },
        'typeA': {
          'fulfills': ['typeB'],
        },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
    });

    test('Super defined second', function() {
      const hierarchy = new TypeHierarchy({
        'A': {
          'fulfills': ['B'],
        },
        'B': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('A', 'B'));
    });

    test('Multiple supers', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB', 'typeC', 'typeD'],
        },
        'typeB': { },
        'typeC': { },
        'typeD': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeC'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeD'));
    });

    test('Deep super', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeB': {
          'fulfills': ['typeC'],
        },
        'typeC': {
          'fulfills': ['typeD'],
        },
        'typeD': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeC'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeD'));
    });

    test('Case', function() {
      const hierarchy = new TypeHierarchy({
        'typeA': {
          'fulfills': ['typeB'],
        },
        'typeb': { },
      });
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeb'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typeA', 'typeB'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typea', 'typeb'));
      chai.assert.isTrue(hierarchy.typeFulfillsType('typea', 'typeB'));
    });
  });

  suite('nearestCommonParents', function() {
    suite('Variable Arguments', function() {
      test('No args', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
        });
        const union = hierarchy.getNearestCommonParents();
        chai.assert.isArray(union);
        chai.assert.isEmpty(union);
      });

      test('One arg', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
        });
        const union = hierarchy.getNearestCommonParents('typeA');
        chai.assert.deepEqual(union, ['typea']);
      });
    });

    suite('Simple tree unions', function() {
      test('Unify self', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
        });
        const union = hierarchy.getNearestCommonParents('typeA', 'typeA');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify parent', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeB', 'typeA');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify parsib', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeD', 'typeC');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify grandparent', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeB'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeC', 'typeA');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify grandparsib', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
          'typeE': {
            'fulfills': ['typeD'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeE', 'typeC');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify sibling', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeB', 'typeC');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify cousin', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
          'typeE': {
            'fulfills': ['typeC'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeD', 'typeE');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify second cousin', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
          'typeE': {
            'fulfills': ['typeC'],
          },
          'typeF': {
            'fulfills': ['typeD'],
          },
          'typeG': {
            'fulfills': ['typeE'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeF', 'typeG');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify first cousin once removed', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
          'typeE': {
            'fulfills': ['typeC'],
          },
          'typeF': {
            'fulfills': ['typeD'],
          },
          'typeG': {
            'fulfills': ['typeE'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeD', 'typeG');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify child', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeA', 'typeB');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify nibling', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeC', 'typeD');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify grandnibling', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {
            'fulfills': ['typeA'],
          },
          'typeC': {
            'fulfills': ['typeA'],
          },
          'typeD': {
            'fulfills': ['typeB'],
          },
          'typeE': {
            'fulfills': ['typeD'],
          },
        });
        const union = hierarchy.getNearestCommonParents('typeC', 'typeE');
        chai.assert.deepEqual(union, ['typea']);
      });

      test('Unify unrelated', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {},
        });
        const union = hierarchy.getNearestCommonParents('typeA', 'typeB');
        chai.assert.isArray(union);
        chai.assert.isEmpty(union);
      });
    });

    /* All of these tests use the following graph. Children are below their
     * parents, except in the case of W and V. In that case there is an arrow
     * to indicate that V is a child of W.
     *
     *  .------U----.
     *  |       \   |
     *  |  W---->V  |
     *  |  |\    |  |
     *  |  | \   |  |
     *  \  |  Z  |  Q
     *   \ | / \ | /
     *    \|/   \|/
     *     X     Y
     */
    suite('Harder graph unions', function() {
      const hierarchy = new TypeHierarchy({
        'typeU': {},
        'typeW': {},
        'typeQ': {
          'fulfills': ['typeU'],
        },
        'typeV': {
          'fulfills': ['typeU', 'typeW'],
        },
        'typeZ': {
          'fulfills': ['typeW'],
        },
        'typeX': {
          'fulfills': ['typeU', 'typeW', 'typeZ'],
        },
        'typeY': {
          'fulfills': ['typeZ', 'typeV', 'typeQ'],
        },
      });

      test('X and Y', function() {
        const union = hierarchy.getNearestCommonParents('typeX', 'typeY');
        chai.assert.deepEqual(union, ['typez', 'typeu']);
      });

      test('X, Y and Z', function() {
        const union = hierarchy.getNearestCommonParents(
            'typeX', 'typeY', 'typeZ');
        chai.assert.deepEqual(union, ['typez']);
      });

      test('X, Y and W', function() {
        const union = hierarchy.getNearestCommonParents(
            'typeX', 'typeY', 'typeW');
        chai.assert.deepEqual(union, ['typew']);
      });

      test('X, Y and V', function() {
        const union = hierarchy.getNearestCommonParents(
            'typeX', 'typeY', 'typeV');
        chai.assert.deepEqual(union, ['typew', 'typeu']);
      });

      test('U and W', function() {
        const union = hierarchy.getNearestCommonParents('typeU', 'typeW');
        chai.assert.isArray(union);
        chai.assert.isEmpty(union);
      });
    });

    suite('Multiparent unions', function() {
      suite('Two parents, two children', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {},
          'typeC': {
            'fulfills': ['typeA', 'typeB'],
          },
          'typeD': {
            'fulfills': ['typeA', 'typeB'],
          },
        });

        test('C and D', function() {
          const union = hierarchy.getNearestCommonParents('typeC', 'typeD');
          chai.assert.deepEqual(union, ['typea', 'typeb']);
        });

        test('C, D and A', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeC', 'typeD', 'typeA');
          chai.assert.deepEqual(union, ['typea']);
        });

        test('A and B', function() {
          const union = hierarchy.getNearestCommonParents('typeA', 'typeB');
          chai.assert.isArray(union);
          chai.assert.isEmpty(union);
        });
      });

      suite('Three parents, three children', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {},
          'typeC': {},
          'typeD': {
            'fulfills': ['typeA', 'typeB'],
          },
          'typeE': {
            'fulfills': ['typeA', 'typeB', 'typeC'],
          },
          'typeF': {
            'fulfills': ['typeB', 'typeC'],
          },
        });

        test('D and E', function() {
          const union = hierarchy.getNearestCommonParents('typeD', 'typeE');
          chai.assert.deepEqual(union, ['typea', 'typeb']);
        });

        test('E and F', function() {
          const union = hierarchy.getNearestCommonParents('typeE', 'typeF');
          chai.assert.deepEqual(union, ['typeb', 'typec']);
        });

        test('D and F', function() {
          const union = hierarchy.getNearestCommonParents('typeD', 'typeF');
          chai.assert.deepEqual(union, ['typeb']);
        });

        test('D, E and F', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeD', 'typeE', 'typeF');
          chai.assert.deepEqual(union, ['typeb']);
        });

        test('D, E and A', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeD', 'typeE', 'typeA');
          chai.assert.deepEqual(union, ['typea']);
        });

        test('D, E, F and B', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeD', 'typeE', 'typeF', 'typeB');
          chai.assert.deepEqual(union, ['typeb']);
        });

        test('D, E and C', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeD', 'typeE', 'typeC');
          chai.assert.isArray(union);
          chai.assert.isEmpty(union);
        });
      });

      suite('Two layers', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {},
          'typeC': {},
          'typeD': {
            'fulfills': ['typeA', 'typeB'],
          },
          'typeE': {
            'fulfills': ['typeA', 'typeB', 'typeC'],
          },
          'typeF': {
            'fulfills': ['typeB', 'typeC'],
          },
          'typeG': {
            'fulfills': ['typeD', 'typeE'],
          },
          'typeH': {
            'fulfills': ['typeD', 'typeE'],
          },
        });

        test('G and H', function() {
          const union = hierarchy.getNearestCommonParents('typeG', 'typeH');
          chai.assert.deepEqual(union, ['typed', 'typee']);
        });

        test('G, H and F', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeG', 'typeH', 'typeF');
          chai.assert.deepEqual(union, ['typeb', 'typec']);
        });
      });

      suite('Three layers', function() {
        const hierarchy = new TypeHierarchy({
          'typeA': {},
          'typeB': {},
          'typeC': {},
          'typeD': {},
          'typeE': {
            'fulfills': ['typeA', 'typeB'],
          },
          'typeF': {
            'fulfills': ['typeA', 'typeB', 'typeC'],
          },
          'typeG': {
            'fulfills': ['typeB', 'typeC', 'typeD'],
          },
          'typeH': {
            'fulfills': ['typeC', 'typeD'],
          },
          'typeI': {
            'fulfills': ['typeE', 'typeF'],
          },
          'typeJ': {
            'fulfills': ['typeE', 'typeF', 'typeG'],
          },
          'typeK': {
            'fulfills': ['typeF', 'typeG'],
          },
          'typeL': {
            'fulfills': ['typeI', 'typeJ'],
          },
          'typeM': {
            'fulfills': ['typeI', 'typeJ'],
          },
        });

        test('L and M', function() {
          const union = hierarchy.getNearestCommonParents('typeL', 'typeM');
          chai.assert.deepEqual(union, ['typei', 'typej']);
        });

        test('L, M and K', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeL', 'typeM', 'typeK');
          chai.assert.deepEqual(union, ['typef', 'typeg']);
        });

        test('L, M, K and H', function() {
          const union = hierarchy.getNearestCommonParents(
              'typeL', 'typeM', 'typeK', 'typeH');
          chai.assert.deepEqual(union, ['typec', 'typed']);
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { bemClass } from '../../helpers';

class ContentColumn extends Component {
  render() {
    const { flex, wide, narrow } = this.props;
    const classNames =
        `${bemClass('content-column', narrow, '--narrow')} ${
        bemClass('content-column', flex, '--flex')} ${
        bemClass('content-column', wide, '--wide')} ${
        this.props.className || ''}`;

    return (
      <div className={`${classNames} ${this.props.ClassName}`} style={this.props.style}>
        {this.props.children}

    );
  }
}

export default ContentColumn;
<commit_msg>#6184 Fix ContentColumn to ensure it passes in classNames, without duplication<commit_after>import React, { Component } from 'react';
import { bemClass } from '../../helpers';

class ContentColumn extends Component {
  render() {
    const { flex, wide, narrow, className } = this.props;
    const classNames = [];
    classNames.push('content-column');
    if (narrow) { classNames.push(bemClass('content-column', narrow, '--narrow')); }
    if (flex) { classNames.push(bemClass('content-column', flex, '--flex')); }
    if (wide) { classNames.push(bemClass('content-column', wide, '--wide')); }
    classNames.push(className || '');

    return (

        {this.props.children}

    );
  }
}

export default ContentColumn;
<|endoftext|>


<commit_before>import Immutable from 'immutable';
import alt from '../alt';
import data from '../data';
import FundStore from './FundStore';
import CartActions from '../actions/CartActions';
import CartStore from './CartStore';

// Initial state of product so we can reuse it,
// when the CartActions dispatches `clear`
const initial = Immutable.fromJS(data);
// A snapshot of the product each time it is checked out.
// The snapshot allows us to restore to the data after
// a checkout when the cart is cleared.
var snapshot;

class ProductStore {
  constructor() {
    this.products = initial;
    this.bindActions(CartActions);

    // We'll expose this store's getter for CartStore's use
    this.exportPublicMethods({ getProductIndex: this.getProductIndex.bind(this) });
  }

  onAdd(id) {
    // Let the cart get a stock first ;)
    this.waitFor([CartStore, FundStore]);

    try {
      var index = this.getProductIndex(id);
    } catch(e) {
      return false;
    }
    var product = this.products.get(index);

    // OFC, We won't update anything when
    // the stock is already replenished!
    if ( product.get('stock') == 0 ) {
      return false;
    }

    this.products = this.products.update(index, (product) => {
      return product.update('stock', v => v - 1);
    });
  }

  onClear() {
    // Simply reassign our `products` state
    // to `_products` since we created a snapshot
    // of its init state on the class constructor
    this.products = snapshot !== undefined
      ? snapshot
      : initial;
  }

  onCheckout() {
    // Save the snapshot of the current product
    // for later usage (to be used to restore)
    // when cleared
    snapshot = this.products;

    // We're not updating anything.
    return false;
  }

  /**
   * Get the product with the given id
   *
   * @param {int} id ID the of the given product
   * @throws Error
   * @return {Object}
   */
  getProductIndex(id) {
    const index = this.products
      .findIndex(product => product.get('id') === id);

    // Throw an error if the product does not exist
    // >> `findIndex` returns -1 when an index was not found
    if ( index == -1 ) {
      throw new Error(`Product with the id ${id} does not exist`);
    }

    return index;
  }
}

export default alt.createStore(ProductStore);
<commit_msg>Hopefully fixes https://github.com/srph/jumars-tindahan/issues/3.<commit_after>import Immutable from 'immutable';
import alt from '../alt';
import data from '../data';
import FundStore from './FundStore';
import CartActions from '../actions/CartActions';
import CartStore from './CartStore';

// Initial state of product so we can reuse it,
// when the CartActions dispatches `clear`
const initial = Immutable.fromJS(data);
// A snapshot of the product each time it is checked out.
// The snapshot allows us to restore to the data after
// a checkout when the cart is cleared.
var snapshot;

class ProductStore {
  constructor() {
    this.products = initial;
    this.bindActions(CartActions);

    // We'll expose this store's getter for CartStore's use
    this.exportPublicMethods({ getProductIndex: this.getProductIndex.bind(this) });
  }

  onAdd(id) {
    // Let the cart get a stock first ;)
    this.waitFor([CartStore, FundStore]);
    var funds = FundStore.getState().funds;

    try {
      var index = this.getProductIndex(id);
    } catch(e) {
      return false;
    }

    var product = this.products.get(index);
    var stock = product.get('stock');
    var diff = funds - product.get('price');

    // OFC, We won't update anything when
    // the stock is already replenished!
    if ( stock == 0 || diff < 0 ) {
      return false;
    }

    this.products = this.products.update(index, (product) => {
      return product.update('stock', v => v - 1);
    });
  }

  onClear() {
    // Simply reassign our `products` state
    // to `_products` since we created a snapshot
    // of its init state on the class constructor
    this.products = snapshot !== undefined
      ? snapshot
      : initial;
  }

  onCheckout() {
    // Save the snapshot of the current product
    // for later usage (to be used to restore)
    // when cleared
    snapshot = this.products;

    // We're not updating anything.
    return false;
  }

  /**
   * Get the product with the given id
   *
   * @param {int} id ID the of the given product
   * @throws Error
   * @return {Object}
   */
  getProductIndex(id) {
    const index = this.products
      .findIndex(product => product.get('id') === id);

    // Throw an error if the product does not exist
    // >> `findIndex` returns -1 when an index was not found
    if ( index == -1 ) {
      throw new Error(`Product with the id ${id} does not exist`);
    }

    return index;
  }
}

export default alt.createStore(ProductStore);
<|endoftext|>


<commit_before>angular.module("mopify.services.playlistmanager", [
    'mopify.services.mopidy',
    'mopify.services.servicemanager',
    'mopify.services.settings',
    "spotify"
])

.factory("PlaylistManager", function($rootScope, $q, $interval, ServiceManager, Spotify, mopidyservice, Settings){
    "use strict";

    function PlaylistManager(){
        var that = this;

        this.source = "";
        this.playlists = [];
        this.orderedPlaylists = {};
        this.loading = true;
        that.spotifyuserid = null;

        // Load playlists via Spotify or Mopidy depending on the settings
        var loadspotifyplaylists = false;

        if(Settings.get("spotify") !== undefined)
            loadspotifyplaylists = Settings.get("spotify").loadspotifyplaylists;

        if(ServiceManager.isEnabled("spotify") && loadspotifyplaylists === true){
            // Load when mopidy is online
            $rootScope.$on("mopify:spotify:connected", function(){
                Spotify.getCurrentUser().then(function(user){
                    that.spotifyuserid = user.id;
                    that.loadPlaylists();
                });
            });
        }
        else{
            $rootScope.$on("mopidy:state:online", function(){
                that.loadPlaylists();
            });

            if(mopidyservice.isConnected){
                that.loadPlaylists();
            }
        }

        // Load playlists on playlists:change event
        $rootScope.$on("mopidy:event:playlistsLoaded", function(){
            // Load playlists
            that.loadPlaylists();
        });
    }

    /**
     * Load the playlists from Spotify or Mopidy
     */
    PlaylistManager.prototype.loadPlaylists = function() {
        var that = this;

        // Set loading
        this.loading = true;

        // Clear current playlists array
        this.playlists = [];
        this.orderedPlaylists = {};

        // Get the spotify loadplaylists setting
        var loadspotifyplaylists = false;

        if(Settings.get("spotify") !== undefined)
            loadspotifyplaylists = Settings.get("spotify").loadspotifyplaylists;

        // Load the playlists from Spotify is the user is connected, otherwise load them from Mopidy
        if(ServiceManager.isEnabled("spotify") && loadspotifyplaylists === true){
            // Set source to spotify
            this.source = "spotify";

            // Get user's playlists
            Spotify.getUserPlaylists(that.spotifyuserid, { limit: 50 }).then(function(data){
                that.playlists = data.items;

                // Starts loading more playlists if needed
                if(data.next !== null){
                    that.loadMorePlaylists(data.next);
                }
                else{
                    that.playlists = sortPlaylists(that.playlists);
                    that.loading = false;
                }
            });
        }
        else{
            // Set source to mopidy
            this.source = "mopidy";

            mopidyservice.getPlaylists().then(function(playlists){
                that.playlists = sortPlaylists(playlists);
                that.orderedPlaylists = orderPlaylists(playlists);
                that.loading = false;
            });
        }
    };

    /**
     * Return the previously loaded playlists
     * @param  {object} options extra options for the returned object
     * @return {array}         the playlists
     */
    PlaylistManager.prototype.getPlaylists = function(options){
        var deferred = $q.defer();
        var that = this;

        options = options || {};

        if(!that.loading){
            var playlists = that.playlists;

            if(options.ordered === true)
                playlists = that.orderedPlaylists;

            if(options.useronly === true && options.ordered !== true){
                playlists = _.filter(that.playlists, function(playlist){
                    return (playlist.uri.indexOf(that.spotifyuserid) > 0);
                });
            }

            deferred.resolve(playlists);
        }
        else{
            var loadinginterval = $interval(function(){
                if(!that.loading){
                    $interval.cancel(loadinginterval);

                    var playlists = that.playlists;

                    if(options.ordered === true)
                        playlists = that.orderedPlaylists;

                    if(options.useronly === true && options.ordered !== true){
                        playlists = _.filter(that.playlists, function(playlist){
                            return (playlist.uri.indexOf(that.spotifyuserid) > 0);
                        });
                    }

                    deferred.resolve(playlists);
                }
            }, 300);
        }

        return deferred.promise;
    };

    /**
     * Load more playlists
     * This is used when spotify playlists are loaded and the next attribute is present
     * @param {string} next The url of the next page
     */
    PlaylistManager.prototype.loadMorePlaylists = function(next){
        var that = this;

        Spotify.api(next.replace("https://api.spotify.com/v1", ""), 'GET', null, {}, {
            'Authorization': 'Bearer ' + Spotify.authToken,
            'Content-Type': 'application/json'
        }).then(function(data){
            // Starts loading more playlists if needed
            if(data.next !== null){
                that.loadMorePlaylists(data.next);
            }
            else{
                that.playlists = sortPlaylists(that.playlists.concat(data.items));
                that.loading = false;
            }
        });
    };

    /**
     * Remove a track from a playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} trackuri   The spotify track URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.removeTrack = function(playlistid, trackuri){
        var deferred = $q.defer();

        if(ServiceManager.isEnabled("spotify")){
            Spotify.removePlaylistTracks(this.spotifyuserid, playlistid, trackuri).then(function(response){
                deferred.resolve(response);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Add a track to a playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} trackuri   The spotify track URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.addTrack = function(playlistid, trackuri){
        var deferred = $q.defer();

        if(ServiceManager.isEnabled("spotify")){
            Spotify.addPlaylistTracks(this.spotifyuserid, playlistid, trackuri).then(function(response){
                deferred.resolve(response);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Add a album to the playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} albumuri   The spotify album URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.addAlbum = function(playlistid, albumuri){
        var deferred = $q.defer();
        var that = this;

        if(ServiceManager.isEnabled("spotify")){
            Spotify.getAlbumTracks(albumuri, {
                limit: 50
            }).then(function(data){
                var trackuris = _.map(data.items, function(item){
                    return item.uri;
                });

                Spotify.addPlaylistTracks(that.spotifyuserid, playlistid, trackuris).then(function(response){
                    deferred.resolve(response);
                });
            });

        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    PlaylistManager.prototype.createPlaylist = function(name){
        var deferred = $q.defer();
        var that = this;

        if(ServiceManager.isEnabled("spotify")){
            Spotify.createPlaylist(that.spotifyuserid, {
                name: name
            }).then(function(response){
                deferred.resolve(response);

                // Add playlist to playlists
                that.playlists.push(response);
                that.playlists = sortPlaylists(that.playlists);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Create an array containing all the folders
     * //TODO: Add support for multi dimmension folders
     *
     * @param {array} playlists array containing playlists
     * @return {object}          object containing folders and playlists
     */
    function orderPlaylists(playlists){
        var resultfolders = {rest: []};

        _.each(playlists, function(playlist){
            // Check if we have to create the folder
            var splittedname = playlist.name.split("/", 2);
            var foldername = splittedname[0];

            if(splittedname.length > 1){
                // Override the playlist name
                playlist.name = splittedname[1];

                // Create a folder and add the playlist, or add it to the existing folder
                if(resultfolders[foldername] === undefined)
                    resultfolders[foldername] = [playlist];
                else
                    resultfolders[foldername].push(playlist);
            }
            else{
                resultfolders.rest.push(playlist);
            }
        });

        return resultfolders;
    }

    /**
     * Sort the playlist from A to Z
     */
    function sortPlaylists(playlists){
        return playlists.sort(function(a, b){
            if(a.name.toLowerCase() < b.name.toLowerCase()) return -1;
            if(a.name.toLowerCase() > b.name.toLowerCase()) return 1;
            return 0;
        });
    }

    return new PlaylistManager();
});<commit_msg>Concat items when received, fixed  #122<commit_after>angular.module("mopify.services.playlistmanager", [
    'mopify.services.mopidy',
    'mopify.services.servicemanager',
    'mopify.services.settings',
    "spotify"
])

.factory("PlaylistManager", function($rootScope, $q, $interval, ServiceManager, Spotify, mopidyservice, Settings){
    "use strict";

    function PlaylistManager(){
        var that = this;

        this.source = "";
        this.playlists = [];
        this.orderedPlaylists = {};
        this.loading = true;
        that.spotifyuserid = null;

        // Load playlists via Spotify or Mopidy depending on the settings
        var loadspotifyplaylists = false;

        if(Settings.get("spotify") !== undefined)
            loadspotifyplaylists = Settings.get("spotify").loadspotifyplaylists;

        if(ServiceManager.isEnabled("spotify") && loadspotifyplaylists === true){
            // Load when mopidy is online
            $rootScope.$on("mopify:spotify:connected", function(){
                Spotify.getCurrentUser().then(function(user){
                    that.spotifyuserid = user.id;
                    that.loadPlaylists();
                });
            });
        }
        else{
            $rootScope.$on("mopidy:state:online", function(){
                that.loadPlaylists();
            });

            if(mopidyservice.isConnected){
                that.loadPlaylists();
            }
        }

        // Load playlists on playlists:change event
        $rootScope.$on("mopidy:event:playlistsLoaded", function(){
            // Load playlists
            that.loadPlaylists();
        });
    }

    /**
     * Load the playlists from Spotify or Mopidy
     */
    PlaylistManager.prototype.loadPlaylists = function() {
        var that = this;

        // Set loading
        this.loading = true;

        // Clear current playlists array
        this.playlists = [];
        this.orderedPlaylists = {};

        console.log(this.playlists);

        // Get the spotify loadplaylists setting
        var loadspotifyplaylists = false;

        if(Settings.get("spotify") !== undefined)
            loadspotifyplaylists = Settings.get("spotify").loadspotifyplaylists;

        // Load the playlists from Spotify is the user is connected, otherwise load them from Mopidy
        if(ServiceManager.isEnabled("spotify") && loadspotifyplaylists === true){
            // Set source to spotify
            this.source = "spotify";

            // Get user's playlists
            Spotify.getUserPlaylists(that.spotifyuserid, { limit: 50 }).then(function(data){
                that.playlists = data.items;

                // Starts loading more playlists if needed
                if(data.next !== null){
                    that.loadMorePlaylists(data.next);
                }
                else{
                    that.playlists = sortPlaylists(that.playlists);
                    that.loading = false;
                }
            });
        }
        else{
            // Set source to mopidy
            this.source = "mopidy";

            mopidyservice.getPlaylists().then(function(playlists){
                that.playlists = sortPlaylists(playlists);
                that.orderedPlaylists = orderPlaylists(playlists);
                that.loading = false;
            });
        }
    };

    /**
     * Return the previously loaded playlists
     * @param  {object} options extra options for the returned object
     * @return {array}         the playlists
     */
    PlaylistManager.prototype.getPlaylists = function(options){
        var deferred = $q.defer();
        var that = this;

        options = options || {};

        if(!that.loading){
            var playlists = that.playlists;

            if(options.ordered === true)
                playlists = that.orderedPlaylists;

            if(options.useronly === true && options.ordered !== true){
                playlists = _.filter(that.playlists, function(playlist){
                    return (playlist.uri.indexOf(that.spotifyuserid) > 0);
                });
            }

            deferred.resolve(playlists);
        }
        else{
            var loadinginterval = $interval(function(){
                if(!that.loading){
                    $interval.cancel(loadinginterval);

                    var playlists = that.playlists;

                    if(options.ordered === true)
                        playlists = that.orderedPlaylists;

                    if(options.useronly === true && options.ordered !== true){
                        playlists = _.filter(that.playlists, function(playlist){
                            return (playlist.uri.indexOf(that.spotifyuserid) > 0);
                        });
                    }

                    deferred.resolve(playlists);
                }
            }, 300);
        }

        return deferred.promise;
    };

    /**
     * Load more playlists
     * This is used when spotify playlists are loaded and the next attribute is present
     * @param {string} next The url of the next page
     */
    PlaylistManager.prototype.loadMorePlaylists = function(next){
        var that = this;

        console.log("Loading more playlists");

        Spotify.api(next.replace("https://api.spotify.com/v1", ""), 'GET', null, {}, {
            'Authorization': 'Bearer ' + Spotify.authToken,
            'Content-Type': 'application/json'
        }).then(function(data){
            // Concat items
            that.playlists = sortPlaylists(that.playlists.concat(data.items));

            // Starts loading more playlists if needed
            if(data.next !== null){
                that.loadMorePlaylists(data.next);
            }
            else{
                that.loading = false;
            }
        });
    };

    /**
     * Remove a track from a playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} trackuri   The spotify track URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.removeTrack = function(playlistid, trackuri){
        var deferred = $q.defer();

        if(ServiceManager.isEnabled("spotify")){
            Spotify.removePlaylistTracks(this.spotifyuserid, playlistid, trackuri).then(function(response){
                deferred.resolve(response);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Add a track to a playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} trackuri   The spotify track URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.addTrack = function(playlistid, trackuri){
        var deferred = $q.defer();

        if(ServiceManager.isEnabled("spotify")){
            Spotify.addPlaylistTracks(this.spotifyuserid, playlistid, trackuri).then(function(response){
                deferred.resolve(response);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Add a album to the playlist
     * @param  {string} playlistid The id of the spotify playlist
     * @param  {string} albumuri   The spotify album URI
     * @return {$q.defer}
     */
    PlaylistManager.prototype.addAlbum = function(playlistid, albumuri){
        var deferred = $q.defer();
        var that = this;

        if(ServiceManager.isEnabled("spotify")){
            Spotify.getAlbumTracks(albumuri, {
                limit: 50
            }).then(function(data){
                var trackuris = _.map(data.items, function(item){
                    return item.uri;
                });

                Spotify.addPlaylistTracks(that.spotifyuserid, playlistid, trackuris).then(function(response){
                    deferred.resolve(response);
                });
            });

        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    PlaylistManager.prototype.createPlaylist = function(name){
        var deferred = $q.defer();
        var that = this;

        if(ServiceManager.isEnabled("spotify")){
            Spotify.createPlaylist(that.spotifyuserid, {
                name: name
            }).then(function(response){
                deferred.resolve(response);

                // Add playlist to playlists
                that.playlists.push(response);
                that.playlists = sortPlaylists(that.playlists);
            });
        }
        else{
            deferred.reject();
        }

        return deferred.promise;
    };

    /**
     * Create an array containing all the folders
     * //TODO: Add support for multi dimmension folders
     *
     * @param {array} playlists array containing playlists
     * @return {object}          object containing folders and playlists
     */
    function orderPlaylists(playlists){
        var resultfolders = {rest: []};

        _.each(playlists, function(playlist){
            // Check if we have to create the folder
            var splittedname = playlist.name.split("/", 2);
            var foldername = splittedname[0];

            if(splittedname.length > 1){
                // Override the playlist name
                playlist.name = splittedname[1];

                // Create a folder and add the playlist, or add it to the existing folder
                if(resultfolders[foldername] === undefined)
                    resultfolders[foldername] = [playlist];
                else
                    resultfolders[foldername].push(playlist);
            }
            else{
                resultfolders.rest.push(playlist);
            }
        });

        return resultfolders;
    }

    /**
     * Sort the playlist from A to Z
     */
    function sortPlaylists(playlists){
        return playlists.sort(function(a, b){
            if(a.name.toLowerCase() < b.name.toLowerCase()) return -1;
            if(a.name.toLowerCase() > b.name.toLowerCase()) return 1;
            return 0;
        });
    }

    return new PlaylistManager();
});<|endoftext|>


<commit_before>Template.signup.rendered = function() {
    if(!this._rendered) {
        this._rendered = true;
        Session.set('aecinput', 'none');
        Session.set('aecconfirm', 'none');
        Session.set('aeciframe', 'block');
        Session.set('finalPage', 'none');

    }
}

Template.signup.events({
    'click #copied': function () {
       Session.set('aeciframe', 'none');
       Session.set('aecinput', 'block');
   },
    'click #p1': function () {
        Session.set('aeciframe', 'block');
        Session.set('aecinput', 'none');
    },

    'submit form': function(e) {
        e.preventDefault();
        var textdata = $(e.target).find('[name=usertext]').val();
        if (textdata.indexOf("Given names") >=0){

        var string = textdata.split("\n").join(",");
        var arraylist = string.split(",");
        for (var i = 0; i < arraylist.length; i++) {

            if(arraylist[i].indexOf('Given names') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('givenName', temp[1].trim());
            };
            if(arraylist[i].indexOf('Family') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('familyName', temp[1].trim());
            };
            if(arraylist[i].indexOf('Locality') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('locality', temp[1].trim());
            };
            if(arraylist[i].indexOf('Federal Division') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('federalDivision', temp[1].trim());
            };
            if(arraylist[i].indexOf('Local Government') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('localCouncil', temp[1].trim());
            };
            if(arraylist[i].indexOf('State District') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('stateDistrict', temp[1].trim());
            };
            if(arraylist[i].indexOf('Ward') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('ward', temp[1].trim());
            };

            Session.set('aecinput', 'none');
            Session.set('aecconfirm', 'block');

        }; } else {alert("The text you pasted did not contain the correct details. Please try again.")};

},
    'click #toLast': function () {
        Session.set('finalPage', 'block');
        Session.set('aecinput', 'none');
        Session.set('aecconfirm', 'none');
        Session.set('aeciframe', 'none');

    }
});

Template.signup.helpers({

    data: function (variable) {
    return Session.get(variable);
    },

    display: function(block) {
        return Session.get(block);
    }
});
<commit_msg>Built framework to insert AEC data into a Collection<commit_after>Template.signup.rendered = function() {
    if(!this._rendered) {
        this._rendered = true;
        Session.set('aecinput', 'none');
        Session.set('aecconfirm', 'none');
        Session.set('aeciframe', 'block');
        Session.set('finalPage', 'none');

    }
}

Template.signup.events({
    'click #copied': function () {
       Session.set('aeciframe', 'none');
       Session.set('aecinput', 'block');
   },
    'click #p1': function () {
        Session.set('aeciframe', 'block');
        Session.set('aecinput', 'none');
    },

    'submit form': function(e) {
        e.preventDefault();
        var textdata = $(e.target).find('[name=usertext]').val();
        if (textdata.indexOf("Given names") >=0){

        var string = textdata.split("\n").join(",");
        var arraylist = string.split(",");
        for (var i = 0; i < arraylist.length; i++) {

            if(arraylist[i].indexOf('Given names') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('givenName', temp[1].trim());
            };
            if(arraylist[i].indexOf('Family') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('familyName', temp[1].trim());
            };
            if(arraylist[i].indexOf('Locality') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('locality', temp[1].trim());
            };
            if(arraylist[i].indexOf('Federal Division') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('federalDivision', temp[1].trim());
            };
            if(arraylist[i].indexOf('Local Government') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('localCouncil', temp[1].trim());
            };
            if(arraylist[i].indexOf('State District') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('stateDistrict', temp[1].trim());
            };
            if(arraylist[i].indexOf('Ward') >= 0){
                var temp = arraylist[i].split(":");
                Session.set('ward', temp[1].trim());
            };

            Session.set('aecinput', 'none');
            Session.set('aecconfirm', 'block');

        }; } else {alert("The text you pasted did not contain the correct details. Please try again.")};

},
    'click #toLast': function () {
        e.preventDefault();
        //Block Display
        Session.set('finalPage', 'block');
        Session.set('aecinput', 'none');
        Session.set('aecconfirm', 'none');
        Session.set('aeciframe', 'none');
        //Build User Profile Data
        var profileData = {
          givenName: $(e.target).find('[name=givenName]').val(),
          familyName: $(e.target).find('[name=familyName]').val(),
          federalDivision: $(e.target).find('[name=federalDivision]').val(),
          stateDistrict: $(e.target).find('[name=stateDistrict]').val(),
          localCouncil: $(e.target).find('[name=localCouncil]').val(),
          locality: $(e.target).find('[name=locality]').val(),
          ward: $(e.target).find('[name=ward]').val()
        };
        Meteor.call('profileInsert', profileData, function(error, result) { // display the error to the user and abort
            if (error)
                return alert(error.reason);
            Router.go('profilePage', {_id: result._id});
        });

    }
});

Template.signup.helpers({

    data: function (variable) {
    return Session.get(variable);
    },

    display: function(block) {
        return Session.get(block);
    }
});
<|endoftext|>