<commit_before>/*******************************************************************************
 *
 * Copyright 2013-2014 Trimble Navigation Ltd.
 * License: The MIT License (MIT)
 *
 ******************************************************************************/

/*******************************************************************************
 *
 * module TestUp.TestSuites
 *
 ******************************************************************************/

var TestUp = TestUp || {};

TestUp.TestSuites = function() {

  // Data structure containing the testsuites, testcases, and tests.
  var testsuites_ = {};

  var last_active_tab_ = null;

  // Public
  return {

    init :  function(config) {
      last_active_tab_ = config.active_tab;

      var $testsuites = $('
');
      var $command_bar = $('
');

      var $command_select_all = $('Select All ');
      $command_select_all.on('click', TestUp.Commands.select_all);
      $command_bar.append($command_select_all);

      var $command_select_none = $('Select None ');
      $command_select_none.on('click', TestUp.Commands.select_none);
      $command_bar.append($command_select_none);

      $testsuites.append($command_bar);
      $testsuites.append('
');
      $('body').append($testsuites);

      // Toggle test cases on/off.
      // * Check/uncheck tests belonging to the testcase.
      $(document).on('change', '.testcase > .title input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
        var $checkbox = $(this);
        var checked = $checkbox.prop('checked');
        var $testcase = $checkbox.parents('.testcase');
        var $tests = $testcase.find('input[type=checkbox]');
        $tests.prop('checked', checked);
      });

      // Toggle tests on/off.
      // * Remove checkmark from testcase when induvidual tests are checked.
      $(document).on('change', '.test > .title input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
        var $checkbox = $(this);
        var checked = $checkbox.prop('checked');
        var $testcase = $checkbox.parents('.testcase');
        var $title = $testcase.children('.title');
        var $testcase_checkbox = $title.find('input[type=checkbox]');
        $testcase_checkbox.prop('checked', false);
      });

      // This is needed to block the roll up/down event of the parent title
      // element from triggering when clicking the checkbox.
      $(document).on('click', '#testsuites input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
      });

      // Roll up/down
      $(document).on('click', '#testsuites .title',
        function(event)
      {
        var $parent = $(this).parent();
        var $container = $parent.children('.container');
        $container.slideToggle('fast');
      });
    },

    active : function() {
      return TestUp.Tabs.selected();
    },

    activate : function(testsuite_name) {
      // Change tab.
      var $tab = TestUp.Tabs.select(testsuite_name);
      last_active_tab_ = $.trim( $tab.text() );
      var $testsuite = testsuite_from_name(last_active_tab_);
      // Update coverage.
      var coverage = $testsuite.data('coverage');
      if (coverage === undefined)
      {
        $('#summary_coverage').hide();
      }
      else
      {
        $('#summary_coverage span').text( Math.round(coverage) + '%' );
        $('#summary_coverage').show();
      }
      // Display testsuite data.
      assert($testsuite.length);
      $('.testsuite').removeClass('active');
      $testsuite.addClass('active');
      TestUp.TestSuite.update_summary();
    },

    count : function() {
      return TestUp.Tabs.count();
    },

    reset : function() {
      $('#testsuite_container').empty();
      $('#tabs').empty();
      testsuites_ = {};
      last_active_tab_ = null;
    },

    update : function(testsuites) {
      testsuites_ = testsuites;
      for (testsuite_name in testsuites_)
      {
        var testsuite = testsuites_[testsuite_name];
        var testsuite_data = testsuite.testcases;
        ensure_tab_exists(testsuite_name);
        $testsuite = ensure_testsuite_exists(testsuite_name);
        update_missing_coverage($testsuite, testsuite);
        TestUp.TestSuite.update($testsuite, testsuite_data);
      }
      TestUp.TestSuites.activate(last_active_tab_);
      TestUp.TestSuite.update_results(false);
      update_discovered_status(testsuites);
    },

    // Because building the HTML content using the DOM is so TERRIBLY slow in IE
    // we must resort to building the content using HTML strings instead.
    // Is is not ideal as there is now a lot of duplicate code!
    update_first_run : function(testsuites) {
      //TestUp.debug('TestUp.TestSuites.update_first_run_hack');
      testsuites_ = testsuites;
      for (testsuite_name in testsuites_)
      {
        var testsuite = testsuites_[testsuite_name];
        ensure_tab_exists(testsuite_name);
        $testsuite = ensure_testsuite_exists(testsuite_name);
        var testsuite_data = merge_missing_coverage(testsuite);
        var html = TestUp.TestSuite.create_html(testsuite_data);
        $testsuite.html(html);
      }
      TestUp.TestSuites.activate(last_active_tab_);
      TestUp.TestSuite.update_results(false);
      update_discovered_status(testsuites);
    }

  };

  // Private

  function update_missing_coverage($testsuite, testsuite)
  {
    var missing = testsuite.missing_coverage;
    // If this testsuite has a coverage manifest, store the computed percent
    // coverage with the testsuite.
    if (missing && Object.keys(missing).length > 0) // Hmm... Is this correct?
    {
      $testsuite.data('coverage', testsuite.coverage);
    }
    for (testcase_name in missing)
    {
      var tests = missing[testcase_name];
      $testcase = TestUp.TestCase.ensure_exist($testsuite, testcase_name);
      for (var i = 0; i < tests.length; ++i)
      {
        var test_name = tests[i];
        var $test = TestUp.Test.ensure_exist($testcase, test_name);
        $test.addClass('missing');
      }
    }
  }

  function merge_missing_coverage(testsuite)
  {
    var missing = testsuite.missing_coverage;
    if (missing && Object.keys(missing).length > 0) // Hmm... Is this correct?
    {
      $testsuite.data('coverage', testsuite.coverage);
    }
    if (missing === undefined || Object.keys(missing).length == 0)
    {
      return testsuite.testcases;
    }

    // Merge and sort testcase names.
    var testcase_names = Object.keys(testsuite.testcases);
    testcase_names = testcase_names.concat(Object.keys(missing));
    testcase_names = testcase_names.sort();
    var testcases = {};
    for (var i = 0; i < testcase_names.length; ++i)
    {
      var testcase_name = testcase_names[i];
      // Avoid duplicates.
      if (testcase_name in testcases)
      {
        continue;
      }

      // Merge and sort the testcase's tests.
      var tests = [];
      if (testcase_name in testsuite.testcases)
      {
        tests = tests.concat(testsuite.testcases[testcase_name])
      }
      if (testcase_name in missing)
      {
        // Note: these names doesn't have the ":" prefix which the
        // testsuite.testcase has.
        tests = tests.concat(missing[testcase_name])
      }
      testcases[testcase_name] = tests.sort();
    }
    return testcases;
  }

  function update_discovered_status(testsuites)
  {
    var num_testsuites = 0;
    var num_testcases = 0;
    var num_tests = 0;

    for (testsuite_name in testsuites)
    {
      var testsuite = testsuites[testsuite_name];
      var testsuite_data = testsuite.testcases;
      ++num_testsuites;
      for (testcase_name in testsuite_data)
      {
        ++num_testcases;
        var tests = testsuite_data[testcase_name];
        num_tests += tests.length;
      }
    }

    TestUp.Statusbar.text(
      num_testsuites + ' test suites, ' +
      num_testcases + ' test cases, ' +
      num_tests + ' tests discovered - ' +
      new Date().toLocaleTimeString()
    );
  }

  function ensure_testsuite_exists(testsuite_name) {
    var $testsuite = testsuite_from_name(testsuite_name);
    if ($testsuite.length == 0)
    {
      var testsuite_id = testsuite_id_from_name(testsuite_name);
      var html = '\

\

\
      ';
      var $testsuite = $(html);
      $('#testsuite_container').append($testsuite);
    }
    return $testsuite;
  }

  function testsuite_from_name(testsuite_name) {
    var id = testsuite_id_from_name(testsuite_name)
    var $testsuite = $('#' + id);
    return $testsuite;
  }

  function testsuite_id_from_name(testsuite_name) {
    var id_name = testsuite_name.replace(/[^a-z0-9]+/ig, '-');
    return 'suite_' + id_name;
  }

  function ensure_tab_exists(tab_text) {
    var $tab = TestUp.Tabs.get(tab_text);
    if ($tab.length == 0)
    {
      $tab = TestUp.Tabs.add(tab_text);
      $tab.on('click', tab_change);
    }
    assert($tab.length);
    return $tab;
  }

  function tab_change() {
    var $tab = $(this);
    var testsuite_name = $.trim( $tab.text() );
    Sketchup.callback('TestUp.TestSuites.on_change', testsuite_name);
    var testsuite_data = testsuites_[testsuite_name];
    TestUp.TestSuites.activate(testsuite_name, testsuite_data);
  }

}(); // TestUp
<commit_msg>Remove an assert in testsuites.js and instead handle it with an if check.<commit_after>/*******************************************************************************
 *
 * Copyright 2013-2014 Trimble Navigation Ltd.
 * License: The MIT License (MIT)
 *
 ******************************************************************************/

/*******************************************************************************
 *
 * module TestUp.TestSuites
 *
 ******************************************************************************/

var TestUp = TestUp || {};

TestUp.TestSuites = function() {

  // Data structure containing the testsuites, testcases, and tests.
  var testsuites_ = {};

  var last_active_tab_ = null;

  // Public
  return {

    init :  function(config) {
      last_active_tab_ = config.active_tab;

      var $testsuites = $('
');
      var $command_bar = $('
');

      var $command_select_all = $('Select All ');
      $command_select_all.on('click', TestUp.Commands.select_all);
      $command_bar.append($command_select_all);

      var $command_select_none = $('Select None ');
      $command_select_none.on('click', TestUp.Commands.select_none);
      $command_bar.append($command_select_none);

      $testsuites.append($command_bar);
      $testsuites.append('
');
      $('body').append($testsuites);

      // Toggle test cases on/off.
      // * Check/uncheck tests belonging to the testcase.
      $(document).on('change', '.testcase > .title input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
        var $checkbox = $(this);
        var checked = $checkbox.prop('checked');
        var $testcase = $checkbox.parents('.testcase');
        var $tests = $testcase.find('input[type=checkbox]');
        $tests.prop('checked', checked);
      });

      // Toggle tests on/off.
      // * Remove checkmark from testcase when induvidual tests are checked.
      $(document).on('change', '.test > .title input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
        var $checkbox = $(this);
        var checked = $checkbox.prop('checked');
        var $testcase = $checkbox.parents('.testcase');
        var $title = $testcase.children('.title');
        var $testcase_checkbox = $title.find('input[type=checkbox]');
        $testcase_checkbox.prop('checked', false);
      });

      // This is needed to block the roll up/down event of the parent title
      // element from triggering when clicking the checkbox.
      $(document).on('click', '#testsuites input[type=checkbox]',
        function(event)
      {
        event.stopPropagation();
      });

      // Roll up/down
      $(document).on('click', '#testsuites .title',
        function(event)
      {
        var $parent = $(this).parent();
        var $container = $parent.children('.container');
        $container.slideToggle('fast');
      });
    },

    active : function() {
      return TestUp.Tabs.selected();
    },

    activate : function(testsuite_name) {
      // Change tab.
      var $tab = TestUp.Tabs.select(testsuite_name);
      last_active_tab_ = $.trim( $tab.text() );
      var $testsuite = testsuite_from_name(last_active_tab_);
      // Update coverage.
      var coverage = $testsuite.data('coverage');
      if (coverage === undefined)
      {
        $('#summary_coverage').hide();
      }
      else
      {
        $('#summary_coverage span').text( Math.round(coverage) + '%' );
        $('#summary_coverage').show();
      }
      // Display testsuite data.
      if ($testsuite.length > 0)
      {
        $('.testsuite').removeClass('active');
        $testsuite.addClass('active');
        TestUp.TestSuite.update_summary();
      }
    },

    count : function() {
      return TestUp.Tabs.count();
    },

    reset : function() {
      $('#testsuite_container').empty();
      $('#tabs').empty();
      testsuites_ = {};
      last_active_tab_ = null;
    },

    update : function(testsuites) {
      testsuites_ = testsuites;
      for (testsuite_name in testsuites_)
      {
        var testsuite = testsuites_[testsuite_name];
        var testsuite_data = testsuite.testcases;
        ensure_tab_exists(testsuite_name);
        $testsuite = ensure_testsuite_exists(testsuite_name);
        update_missing_coverage($testsuite, testsuite);
        TestUp.TestSuite.update($testsuite, testsuite_data);
      }
      TestUp.TestSuites.activate(last_active_tab_);
      TestUp.TestSuite.update_results(false);
      update_discovered_status(testsuites);
    },

    // Because building the HTML content using the DOM is so TERRIBLY slow in IE
    // we must resort to building the content using HTML strings instead.
    // Is is not ideal as there is now a lot of duplicate code!
    update_first_run : function(testsuites) {
      //TestUp.debug('TestUp.TestSuites.update_first_run_hack');
      testsuites_ = testsuites;
      for (testsuite_name in testsuites_)
      {
        var testsuite = testsuites_[testsuite_name];
        ensure_tab_exists(testsuite_name);
        $testsuite = ensure_testsuite_exists(testsuite_name);
        var testsuite_data = merge_missing_coverage(testsuite);
        var html = TestUp.TestSuite.create_html(testsuite_data);
        $testsuite.html(html);
      }
      TestUp.TestSuites.activate(last_active_tab_);
      TestUp.TestSuite.update_results(false);
      update_discovered_status(testsuites);
    }

  };

  // Private

  function update_missing_coverage($testsuite, testsuite)
  {
    var missing = testsuite.missing_coverage;
    // If this testsuite has a coverage manifest, store the computed percent
    // coverage with the testsuite.
    if (missing && Object.keys(missing).length > 0) // Hmm... Is this correct?
    {
      $testsuite.data('coverage', testsuite.coverage);
    }
    for (testcase_name in missing)
    {
      var tests = missing[testcase_name];
      $testcase = TestUp.TestCase.ensure_exist($testsuite, testcase_name);
      for (var i = 0; i < tests.length; ++i)
      {
        var test_name = tests[i];
        var $test = TestUp.Test.ensure_exist($testcase, test_name);
        $test.addClass('missing');
      }
    }
  }

  function merge_missing_coverage(testsuite)
  {
    var missing = testsuite.missing_coverage;
    if (missing && Object.keys(missing).length > 0) // Hmm... Is this correct?
    {
      $testsuite.data('coverage', testsuite.coverage);
    }
    if (missing === undefined || Object.keys(missing).length == 0)
    {
      return testsuite.testcases;
    }

    // Merge and sort testcase names.
    var testcase_names = Object.keys(testsuite.testcases);
    testcase_names = testcase_names.concat(Object.keys(missing));
    testcase_names = testcase_names.sort();
    var testcases = {};
    for (var i = 0; i < testcase_names.length; ++i)
    {
      var testcase_name = testcase_names[i];
      // Avoid duplicates.
      if (testcase_name in testcases)
      {
        continue;
      }

      // Merge and sort the testcase's tests.
      var tests = [];
      if (testcase_name in testsuite.testcases)
      {
        tests = tests.concat(testsuite.testcases[testcase_name])
      }
      if (testcase_name in missing)
      {
        // Note: these names doesn't have the ":" prefix which the
        // testsuite.testcase has.
        tests = tests.concat(missing[testcase_name])
      }
      testcases[testcase_name] = tests.sort();
    }
    return testcases;
  }

  function update_discovered_status(testsuites)
  {
    var num_testsuites = 0;
    var num_testcases = 0;
    var num_tests = 0;

    for (testsuite_name in testsuites)
    {
      var testsuite = testsuites[testsuite_name];
      var testsuite_data = testsuite.testcases;
      ++num_testsuites;
      for (testcase_name in testsuite_data)
      {
        ++num_testcases;
        var tests = testsuite_data[testcase_name];
        num_tests += tests.length;
      }
    }

    TestUp.Statusbar.text(
      num_testsuites + ' test suites, ' +
      num_testcases + ' test cases, ' +
      num_tests + ' tests discovered - ' +
      new Date().toLocaleTimeString()
    );
  }

  function ensure_testsuite_exists(testsuite_name) {
    var $testsuite = testsuite_from_name(testsuite_name);
    if ($testsuite.length == 0)
    {
      var testsuite_id = testsuite_id_from_name(testsuite_name);
      var html = '\

\

\
      ';
      var $testsuite = $(html);
      $('#testsuite_container').append($testsuite);
    }
    return $testsuite;
  }

  function testsuite_from_name(testsuite_name) {
    var id = testsuite_id_from_name(testsuite_name)
    var $testsuite = $('#' + id);
    return $testsuite;
  }

  function testsuite_id_from_name(testsuite_name) {
    var id_name = testsuite_name.replace(/[^a-z0-9]+/ig, '-');
    return 'suite_' + id_name;
  }

  function ensure_tab_exists(tab_text) {
    var $tab = TestUp.Tabs.get(tab_text);
    if ($tab.length == 0)
    {
      $tab = TestUp.Tabs.add(tab_text);
      $tab.on('click', tab_change);
    }
    assert($tab.length);
    return $tab;
  }

  function tab_change() {
    var $tab = $(this);
    var testsuite_name = $.trim( $tab.text() );
    Sketchup.callback('TestUp.TestSuites.on_change', testsuite_name);
    var testsuite_data = testsuites_[testsuite_name];
    TestUp.TestSuites.activate(testsuite_name, testsuite_data);
  }

}(); // TestUp
<|endoftext|>


<commit_before>// # Content
//
// Content related functions.
/*jslint node: true */

'use strict';

var cheerio = require('cheerio'), unfluff = require('unfluff'), crypto = require('crypto'), url = require('url'), _ = require('lodash');

var pubsub = require('../lib/pubsub-client');

var sites = require('./sites.js'), utils = require('./utils.js'), annotations = require('./annotations.js');

exports.indexContentItem = indexContentItem;
exports.saveAnnotations = saveAnnotations;
exports.getRecognizedLinks = getRecognizedLinks;

/**
 * Save an HTML content item, queueing content and creating annotations as required
 *
 * If it's not an HTML item, do nothing and return.
 *
 * After processing, sends updated content notification.
 *
 * @param {object} desc:
 * @param {uri} uri
 * @param {string} content
 * @param {object} headers
 *
 * @param {object} context:
 * @param {string} member
 * @param {uri} refererer
 * @param {array} annotators
 * @param {array} categories
 * @param {boolean} queueOnly - only queue, don't update if item exists
 * @param {string} query - retrieve any existing to update by this query rather than by uri
 * NB only the first query result is processed.
 * @parama {array} preferSource - for a query, prefer any existing source's field values
 * @param {boolean} queueOnly don't index if it already exists (optional)
 * @param {boolean} updating don't notify content change (optional, for annotation)
 * @param {array} deleteExistingAnnotatedBy - delete existing annotations by this user
 * }
**/

function indexContentItem(desc, context, callback) {
// find any existing instance
  var process = function(err, result) {
    var cItem, hItem;
// it has been indexed
    if (result && result._source) {
      if (context.queueOnly) {
        GLOBAL.debug('already indexed', desc.uri);
        callback();
        return;
      }
      // transfer historical data
      hItem = result._source;

      // if we're receiving an incomplete item, update it from existing
      if (!desc.created) {
        desc.created = hItem.created;
        desc.content = desc.content || hItem.content;
        desc.title = context.title || desc.title || hItem.title;
      }
    }

    var validated = validateIncomingContentItem(desc, context);
    if (!validated.ok) {
      console.log('failing indexContentItem', validated);
      callback({ error: validated });
      return;
    }
    var user = validated.user;
    desc.state = validated.state;
    desc.title = validated.title || desc.title;

    cItem = annotations.createContentItem(desc);

    cItem.timestamp = new Date();
    if (hItem) {
      // merge existing properties
      if (deleteExistingAnnotatedBy) {
        process.die('WIP');
        _.pluck(hItem.annotations, { annotatedBy: deleteExistingAnnotatedBy }).forEach(function(a) {
          delete hItem.annotations[a];
        });
      }
      ['annotations', 'visitors', 'referers'].forEach(function(p) {
      //onsole.log(p.blue, 'cItem before', cItem[p].length);
        cItem[p] = cItem[p].concat(hItem[p]);
      //console.log(p.blue, hItem[p].length, 'cItem after', cItem[p].length);
      });
    } else {
      cItem.created = cItem.timestamp;
    }

    cItem.annotations = cItem.annotations.concat(annotationsFromTags(cItem, context));

    if (!cItem.content || cItem.content.length < 1) {
      delete cItem.sha1;
      delete cItem.text;
    } else {
      cItem.sha1 = crypto.createHash('sha1').update(cItem.content, 'utf8').digest('hex');
      cItem.text = utils.getTextFromHtml(cItem.content);
      // deduplicate
      cItem.annotations = _.uniq(cItem.annotations, 'flattened');
      cItem.annotationSummary = {};
      // update annotation summary
      _.forOwn(_.groupBy(cItem.annotations, 'state'), function(vals, key) { cItem.annotationSummary[key] = vals.length; });

      if (cItem.annotationSummary.validated > 0 || cItem.annotationSummary.unvalidated > 0) {
        cItem.state = 'annotated';
      } else {
        cItem.state = 'visited';
      }
    }

    GLOBAL.debug('saving ci'.blue, user.type, user.username, cItem.uri, 'new', (hItem === null), 'visitors'.blue, cItem.visitors.length, 'annos', cItem.annotations.length);
    if (user.type === 'User' && desc.state !== utils.states.content.queued) {
      cItem.visitors.push({ member: context.member, mode: context.mode || 'browser', 'timestamp': new Date().toISOString() });
    }
    if (context.referer) {
      cItem.referers.push(context.referer);
    }

    GLOBAL.svc.indexer.saveContentItem(cItem, function(err, res, cItems) {
      cItem = cItems[0];
      if (err || !cItem) {
        GLOBAL.error('index saveContentitem failed', err);
      } else {
        if (!context.updating && cItem.state !== utils.states.content.queued) {
          var desc = _.clone(cItem);
          desc.text = utils.getTextFromHtml(cItem.content);
          desc.selector = 'body';
          var clientID = GLOBAL.svc.auth.clientIDByUsername(user.username);
          if (!clientID) {
            GLOBAL.error('no clientID', user);
          } else {
            utils.indexDelay(function() {
              pubsub.init({ homepage: GLOBAL.config.HOMEPAGE, clientID: clientID }).item.annotations.update(cItem.uri);
            });
          }
        }
        if (hItem && hItem.sha1 !== cItem.sha1) {
          GLOBAL.svc.indexer.saveItemHistory(cItem, utils.passingError);
        }
      }
      callback(err, res, cItem);
    });
  }

// use query for finding existing to update
  if (context.query) {
    GLOBAL.svc.indexer.formQuery({ query: context.query }, function(err, res) {
      var result;
      if (!err && res && res.hits.total) {
        result = res.hits.hits[0];
        // prefer source fields over desc
        if (context.preferSource) {
          context.preferSource.forEach(function(field) {
            if (result._source[field]) {
              GLOBAL.debug('using result', field, result._source[field]);
              desc[field] = result._source[field];
            }
          });
        }
      }
      process(err, result);
    });
// use URI
  } else {
    GLOBAL.svc.indexer.retrieveByURI(desc.uri, process);
  }
}

// Test an incoming desc and return a string with an error if not ok, or ok: true and desc values.
function validateIncomingContentItem(desc, context) {
  var ret = {ok: true};
  if (!context.member) {
    return 'NO MEMBER';
  }
  if (!desc.uri || desc.uri.indexOf('http') !== 0) {
    return 'indexContentItem not an http id ' + desc.uri;
  }

  ret.state = context.state || desc.state;

  if (!ret.queued && ret.state !== utils.states.content.queued) {
    if (!desc.content) {
      return 'no content for ' + desc.state;
    }

// extract recognizable content
    var extracted = extractContent(desc);
    ret.title = extracted.title || desc.title;
    if (!ret.title) {
      GLOBAL.debug('not indexable content-type', desc.uri);
      return 'not indexable';
    }
  } else {
    ret.title = desc.title;
  }

  ret.user = GLOBAL.svc.auth.getUserByUsername(context.member);
  if (!ret.user) {
    console.log(context.member, GLOBAL.config.users);
    return 'User not found';
  }
  return ret;
}

// add contextual category annotations
//FIXME annotation state
// FIXME rename categories to tags
function annotationsFromTags(cItem, context) {
  var ret = [], anno;
  if (context.categories) {
    context.categories.forEach(function(tag) {
      //  complete anno
      if (tag.type) {
        anno = tag;
      } else {
        anno = {hasTarget: cItem.uri, type: 'category', category: tag};
      }
      anno.hasTarget = cItem.uri;
      anno.state = utils.states.annotations.validated;
      anno.annotatedBy = context.member;
      ret.push(annotations.createAnnotation(anno));

    });
  }
  return ret;
}

// save an individual content item's annotations. accepts an item or array.
// FIXME: use versioning to detect collisions.
function saveAnnotations(target, context, newAnnos, callback) {
  if (!annotations || annotations.length < 1) {
    GLOBAL.error('trying to save 0 annotations');
    callback();
    return;
  }
  GLOBAL.svc.indexer.retrieveByURI(target, function(err, res) {
    if (err) {
     callback(err);
      return;
    }

    if (!res._source) {
      callback({error: 'target doesn\'t exist'});
      return;
    }
    var cItem = res._source;
    var desc = { uri: target, annotations: newAnnos, title: cItem.title, state: cItem.state };
    context.updating = true;
    indexContentItem(desc, context, function(err, res, rItem) {
      if (err) {
        console.log('saveAnnotations err'.red, err);
      }
      callback(err, res, rItem);
    });
  });
}

// find finer content areas if available
// extract content based on site definitions
function extractContent(desc) {
  /*
  // search all possible matches, returning first find
  var founds = sites.findMatch(desc.uri);

  if (founds.length) {
    var $ = cheerio.load(desc.content);
  // use first found extractor
    founds.forEach(function(found) {
      if ($(found.selector)) {
        var content = $(found.selector).html();
        return { selector: found.selector, content: content };
      }
    });
  }
  // ok, let's try unfluff
  var uf = unfluff(desc.content);
  return { selector: 'body', content: desc.content, text: uf.text, title: uf.title };
  */
  var title = getTitle(desc.content, '<title') || getTitle(desc.content, '<TITLE');

  return { selector: 'body', content: desc.content, text: 'NOTEXTRACTED', title: title };
}

// Get an HTML title the old-fashioned way.
function getTitle(content, tag) {
  if (!content) {
    return;
  }
  var ts = content.indexOf(tag);
  if (ts < 0) {
    return;
  }
  var te = content.indexOf('>', ts);
  var cs = content.indexOf('<', te + 1);
  var title = content.substring(te + 1, cs);
  return title.trim();
}

// find recognized links in document and add found as queued
function getRecognizedLinks(cItem) {
  var founds = sites.findMatch(cItem.uri), $, links = {};

  founds.forEach(function(found) {
    ['navLinks', 'resultLinks'].forEach(function(type) {
      if (found[type]) {
        $ = $ || cheerio.load(cItem.content);
        links[type] = [];
        found[type].forEach(function(s) {
          $(s + ' a').map(function(i, link) {
            var href = $(link).attr('href');
            if (href) {
              // absolute-ize relative URLs
              if (href.indexOf('http://') !== 0) {
                href = url.resolve(cItem.uri, href);
              }
              links[type].push(href);
            }
          });
        });
      }
    });
  });
  return links;
}
<commit_msg>add context<commit_after>// # Content
//
// Content related functions.
/*jslint node: true */

'use strict';

var cheerio = require('cheerio'), unfluff = require('unfluff'), crypto = require('crypto'), url = require('url'), _ = require('lodash');

var pubsub = require('../lib/pubsub-client');

var sites = require('./sites.js'), utils = require('./utils.js'), annotations = require('./annotations.js');

exports.indexContentItem = indexContentItem;
exports.saveAnnotations = saveAnnotations;
exports.getRecognizedLinks = getRecognizedLinks;

/**
 * Save an HTML content item, queueing content and creating annotations as required
 *
 * If it's not an HTML item, do nothing and return.
 *
 * After processing, sends updated content notification.
 *
 * @param {object} desc:
 * @param {uri} uri
 * @param {string} content
 * @param {object} headers
 *
 * @param {object} context:
 * @param {string} member
 * @param {uri} refererer
 * @param {array} annotators
 * @param {array} categories
 * @param {boolean} queueOnly - only queue, don't update if item exists
 * @param {string} query - retrieve any existing to update by this query rather than by uri
 * NB only the first query result is processed.
 * @parama {array} preferSource - for a query, prefer any existing source's field values
 * @param {boolean} queueOnly don't index if it already exists (optional)
 * @param {boolean} updating don't notify content change (optional, for annotation)
 * @param {array} deleteExistingAnnotatedBy - delete existing annotations by this user
 * }
**/

function indexContentItem(desc, context, callback) {
// find any existing instance
  var process = function(err, result) {
    var cItem, hItem;
// it has been indexed
    if (result && result._source) {
      if (context.queueOnly) {
        GLOBAL.debug('already indexed', desc.uri);
        callback();
        return;
      }
      // transfer historical data
      hItem = result._source;

      // if we're receiving an incomplete item, update it from existing
      if (!desc.created) {
        desc.created = hItem.created;
        desc.content = desc.content || hItem.content;
        desc.title = context.title || desc.title || hItem.title;
      }
    }

    var validated = validateIncomingContentItem(desc, context);
    if (!validated.ok) {
      console.log('failing indexContentItem', validated);
      callback({ error: validated });
      return;
    }
    var user = validated.user;
    desc.state = validated.state;
    desc.title = validated.title || desc.title;

    cItem = annotations.createContentItem(desc);

    cItem.timestamp = new Date();
    if (hItem) {
      // merge existing properties
      if (context.deleteExistingAnnotatedBy) {
        process.die('WIP');
        _.pluck(hItem.annotations, { annotatedBy: context.deleteExistingAnnotatedBy }).forEach(function(a) {
          delete hItem.annotations[a];
        });
      }
      ['annotations', 'visitors', 'referers'].forEach(function(p) {
      //onsole.log(p.blue, 'cItem before', cItem[p].length);
        cItem[p] = cItem[p].concat(hItem[p]);
      //console.log(p.blue, hItem[p].length, 'cItem after', cItem[p].length);
      });
    } else {
      cItem.created = cItem.timestamp;
    }

    cItem.annotations = cItem.annotations.concat(annotationsFromTags(cItem, context));

    if (!cItem.content || cItem.content.length < 1) {
      delete cItem.sha1;
      delete cItem.text;
    } else {
      cItem.sha1 = crypto.createHash('sha1').update(cItem.content, 'utf8').digest('hex');
      cItem.text = utils.getTextFromHtml(cItem.content);
      // deduplicate
      cItem.annotations = _.uniq(cItem.annotations, 'flattened');
      cItem.annotationSummary = {};
      // update annotation summary
      _.forOwn(_.groupBy(cItem.annotations, 'state'), function(vals, key) { cItem.annotationSummary[key] = vals.length; });

      if (cItem.annotationSummary.validated > 0 || cItem.annotationSummary.unvalidated > 0) {
        cItem.state = 'annotated';
      } else {
        cItem.state = 'visited';
      }
    }

    GLOBAL.debug('saving ci'.blue, user.type, user.username, cItem.uri, 'new', (hItem === null), 'visitors'.blue, cItem.visitors.length, 'annos', cItem.annotations.length);
    if (user.type === 'User' && desc.state !== utils.states.content.queued) {
      cItem.visitors.push({ member: context.member, mode: context.mode || 'browser', 'timestamp': new Date().toISOString() });
    }
    if (context.referer) {
      cItem.referers.push(context.referer);
    }

    GLOBAL.svc.indexer.saveContentItem(cItem, function(err, res, cItems) {
      cItem = cItems[0];
      if (err || !cItem) {
        GLOBAL.error('index saveContentitem failed', err);
      } else {
        if (!context.updating && cItem.state !== utils.states.content.queued) {
          var desc = _.clone(cItem);
          desc.text = utils.getTextFromHtml(cItem.content);
          desc.selector = 'body';
          var clientID = GLOBAL.svc.auth.clientIDByUsername(user.username);
          if (!clientID) {
            GLOBAL.error('no clientID', user);
          } else {
            utils.indexDelay(function() {
              pubsub.init({ homepage: GLOBAL.config.HOMEPAGE, clientID: clientID }).item.annotations.update(cItem.uri);
            });
          }
        }
        if (hItem && hItem.sha1 !== cItem.sha1) {
          GLOBAL.svc.indexer.saveItemHistory(cItem, utils.passingError);
        }
      }
      callback(err, res, cItem);
    });
  }

// use query for finding existing to update
  if (context.query) {
    GLOBAL.svc.indexer.formQuery({ query: context.query }, function(err, res) {
      var result;
      if (!err && res && res.hits.total) {
        result = res.hits.hits[0];
        // prefer source fields over desc
        if (context.preferSource) {
          context.preferSource.forEach(function(field) {
            if (result._source[field]) {
              GLOBAL.debug('using result', field, result._source[field]);
              desc[field] = result._source[field];
            }
          });
        }
      }
      process(err, result);
    });
// use URI
  } else {
    GLOBAL.svc.indexer.retrieveByURI(desc.uri, process);
  }
}

// Test an incoming desc and return a string with an error if not ok, or ok: true and desc values.
function validateIncomingContentItem(desc, context) {
  var ret = {ok: true};
  if (!context.member) {
    return 'NO MEMBER';
  }
  if (!desc.uri || desc.uri.indexOf('http') !== 0) {
    return 'indexContentItem not an http id ' + desc.uri;
  }

  ret.state = context.state || desc.state;

  if (!ret.queued && ret.state !== utils.states.content.queued) {
    if (!desc.content) {
      return 'no content for ' + desc.state;
    }

// extract recognizable content
    var extracted = extractContent(desc);
    ret.title = extracted.title || desc.title;
    if (!ret.title) {
      GLOBAL.debug('not indexable content-type', desc.uri);
      return 'not indexable';
    }
  } else {
    ret.title = desc.title;
  }

  ret.user = GLOBAL.svc.auth.getUserByUsername(context.member);
  if (!ret.user) {
    console.log(context.member, GLOBAL.config.users);
    return 'User not found';
  }
  return ret;
}

// add contextual category annotations
//FIXME annotation state
// FIXME rename categories to tags
function annotationsFromTags(cItem, context) {
  var ret = [], anno;
  if (context.categories) {
    context.categories.forEach(function(tag) {
      //  complete anno
      if (tag.type) {
        anno = tag;
      } else {
        anno = {hasTarget: cItem.uri, type: 'category', category: tag};
      }
      anno.hasTarget = cItem.uri;
      anno.state = utils.states.annotations.validated;
      anno.annotatedBy = context.member;
      ret.push(annotations.createAnnotation(anno));

    });
  }
  return ret;
}

// save an individual content item's annotations. accepts an item or array.
// FIXME: use versioning to detect collisions.
function saveAnnotations(target, context, newAnnos, callback) {
  if (!annotations || annotations.length < 1) {
    GLOBAL.error('trying to save 0 annotations');
    callback();
    return;
  }
  GLOBAL.svc.indexer.retrieveByURI(target, function(err, res) {
    if (err) {
     callback(err);
      return;
    }

    if (!res._source) {
      callback({error: 'target doesn\'t exist'});
      return;
    }
    var cItem = res._source;
    var desc = { uri: target, annotations: newAnnos, title: cItem.title, state: cItem.state };
    context.updating = true;
    indexContentItem(desc, context, function(err, res, rItem) {
      if (err) {
        console.log('saveAnnotations err'.red, err);
      }
      callback(err, res, rItem);
    });
  });
}

// find finer content areas if available
// extract content based on site definitions
function extractContent(desc) {
  /*
  // search all possible matches, returning first find
  var founds = sites.findMatch(desc.uri);

  if (founds.length) {
    var $ = cheerio.load(desc.content);
  // use first found extractor
    founds.forEach(function(found) {
      if ($(found.selector)) {
        var content = $(found.selector).html();
        return { selector: found.selector, content: content };
      }
    });
  }
  // ok, let's try unfluff
  var uf = unfluff(desc.content);
  return { selector: 'body', content: desc.content, text: uf.text, title: uf.title };
  */
  var title = getTitle(desc.content, '<title') || getTitle(desc.content, '<TITLE');

  return { selector: 'body', content: desc.content, text: 'NOTEXTRACTED', title: title };
}

// Get an HTML title the old-fashioned way.
function getTitle(content, tag) {
  if (!content) {
    return;
  }
  var ts = content.indexOf(tag);
  if (ts < 0) {
    return;
  }
  var te = content.indexOf('>', ts);
  var cs = content.indexOf('<', te + 1);
  var title = content.substring(te + 1, cs);
  return title.trim();
}

// find recognized links in document and add found as queued
function getRecognizedLinks(cItem) {
  var founds = sites.findMatch(cItem.uri), $, links = {};

  founds.forEach(function(found) {
    ['navLinks', 'resultLinks'].forEach(function(type) {
      if (found[type]) {
        $ = $ || cheerio.load(cItem.content);
        links[type] = [];
        found[type].forEach(function(s) {
          $(s + ' a').map(function(i, link) {
            var href = $(link).attr('href');
            if (href) {
              // absolute-ize relative URLs
              if (href.indexOf('http://') !== 0) {
                href = url.resolve(cItem.uri, href);
              }
              links[type].push(href);
            }
          });
        });
      }
    });
  });
  return links;
}
<|endoftext|>


<commit_before>define(['thruster/input/mousestate', 'thruster/shapes/point2d'], /** @lends Mouse */ function(MouseState, Point2d, undefined){

	/**
	 * Class responsible for tracking the state of the mouse within a particular DOM element's context.
	 * @class
	 * @memberof thruster.input
	 * @param {Element} element The DOM element to bind to.
	 */
	var Mouse = function(element){
		/**
		 * The DOM element this Mouse is bound to.
		 * @private
		 * @type {Element}
		 */
		var _element = element;

		/**
		 * Current position of the mouse cursor.
		 * @private
		 * @type {thruster.shapes.Point2d}
		 */
		var _cursorPosition = new Point2d(0, 0);

		/**
		 * Position of the mouse cursor during the last update.
		 * @private
		 * @type {thruster.shapes.Point2d}
		 */
		var _previousCursorPosition;

		/**
		 * Current state of the mouse buttons as a bit field.
		 * @private
		 * @type {Number}
		 */
		var _buttonState = 0;

		/**
		 * Value of _buttonState during the last update.
		 * @private
		 * @type {Number}
		 */
		var _previousButtonState;

		/**
		 * The movement of the mouse wheel in the y direction since the last update.
		 * We use delta instead of a cumulative value to avoid any chance of an overflow
		 * (that said, someone would have to scroll a long way to cause an overflow).
		 * @private
		 * @type {Number}
		 */
		var _wheelDelta = 0;

		/**
		 *
		 * @private
		 */
		var _buttonValues = [1, 4, 2];

		/**
		 * MouseState representing the state of the mouse at the beginning of this update loop.
		 * This is used to ensure all instances using the mouse during an update loop see the same state.
		 * @private
		 * @type {thruster.input.MouseState}
		 */
		var _currentState;

		/**
		 * Event handler for mousedown events.
		 * @private
		 */
		var _onMouseDown = function(e){
			e = e || window.event;

			if (e.buttons !== undefined){
				_buttonState = e.buttons;
			} else {
				// Get equivalent e.buttons value.
				var buttons = [1, 4, 2],
					button = buttons[e.button];

				// Store button state as a bit field.
				_buttonState = _buttonState | button;
			}
		};

		/**
		 * Event handler for mouseup events.
		 * @private
		 */
		var _onMouseUp = function(e){
			e = e || window.event;

			if (e.buttons !== undefined){
				_buttonState = e.buttons;
			} else {
				// Get equivalent e.buttons value.
				var buttons = [1, 4, 2],
					button = buttons[e.button];

				// Store button state as a bit field.
				_buttonState = _buttonState & ~button;
			}
		};

		/**
		 * Event handler for mousemove events.
		 * @private
		 */
		var _onMouseMove = function(e){
			e = e || window.event;

			// Update stored positions
			_cursorPosition.x = e.clientX;
			_cursorPosition.y = e.clientY;
		};

		/**
		 * Event handler for wheel events.
		 * Currently wheel delta is only tracked in the y direction.
		 * @private
		 */
		var _onWheel = function(e){
			e = e || window.event;

			// Add to current delta.
			_wheelDelta += e.deltaY;
		};

		/**
		 * Attaches the necessary event handlers to receive incoming mouse events from the bound element.
		 * Called automatically during construction.
		 * @public
		 */
		this.attach = function(){
			_element.addEventListener('mousedown', _onMouseDown, true);
			_element.addEventListener('mouseup',   _onMouseUp,   true);
			_element.addEventListener('mousemove', _onMouseMove, true);
			_element.addEventListener('wheel',     _onWheel,     true);
		};

		/**
		 * Detaches all event listeners created by this instance. This should be called before discarding
		 * the instance to avoid memory leaks.
		 * @public
		 */
		this.detach = function(){
			_element.removeEventListener('mousedown', _onMouseDown, true);
			_element.removeEventListener('mouseup',   _onMouseUp,   true);
			_element.removeEventListener('mousemove', _onMouseMove, true);
			_element.removeEventListener('wheel',     _onWheel,     true);
		};

		/**
		 * Returns the current state of the mouse.
		 * @public
		 * @returns {thruster.input.MouseState}
		 */
		this.getState = function() {
			return _currentState;
		};

		/**
		 * Updates the internal state.
		 * @public
		 */
		this.update = function() {
			_currentState = new MouseState(_cursorPosition, _buttonState, _wheelDelta, _previousCursorPosition, _previousButtonState);

			_wheelDelta = 0;
			_previousButtonState = _buttonState;
			_previousCursorPosition = _cursorPosition.clone(); // Clone to prevent changes from affecting both properties.
		};

		// Constructor
		this.attach();
		this.update();
	};

	return Mouse;
});<commit_msg>Reduced duplicate code in thruster.input.Mouse event handlers.<commit_after>define(['thruster/input/mousestate', 'thruster/shapes/point2d'], /** @lends Mouse */ function(MouseState, Point2d, undefined){

	/**
	 * Class responsible for tracking the state of the mouse within a particular DOM element's context.
	 * @class
	 * @memberof thruster.input
	 * @param {Element} element The DOM element to bind to.
	 */
	var Mouse = function(element){
		/**
		 * The DOM element this Mouse is bound to.
		 * @private
		 * @type {Element}
		 */
		var _element = element;

		/**
		 * Current position of the mouse cursor.
		 * @private
		 * @type {thruster.shapes.Point2d}
		 */
		var _cursorPosition = new Point2d(0, 0);

		/**
		 * Position of the mouse cursor during the last update.
		 * @private
		 * @type {thruster.shapes.Point2d}
		 */
		var _previousCursorPosition;

		/**
		 * Current state of the mouse buttons as a bit field.
		 * @private
		 * @type {Number}
		 */
		var _buttonState = 0;

		/**
		 * Value of _buttonState during the last update.
		 * @private
		 * @type {Number}
		 */
		var _previousButtonState;

		/**
		 * The movement of the mouse wheel in the y direction since the last update.
		 * We use delta instead of a cumulative value to avoid any chance of an overflow
		 * (that said, someone would have to scroll a long way to cause an overflow).
		 * @private
		 * @type {Number}
		 */
		var _wheelDelta = 0;

		/**
		 *
		 * @private
		 */
		var _buttonValues = [1, 4, 2];

		/**
		 * MouseState representing the state of the mouse at the beginning of this update loop.
		 * This is used to ensure all instances using the mouse during an update loop see the same state.
		 * @private
		 * @type {thruster.input.MouseState}
		 */
		var _currentState;

		/**
		 * Event handler for mousedown events.
		 * @private
		 */
		var _onMouseDown = function(e){
			e = e || window.event;

			if (e.buttons !== undefined){
				_buttonState = e.buttons;
			} else {
				// Get equivalent e.buttons value.
				var button = _buttonValues[e.button];

				// Store button state as a bit field.
				_buttonState = _buttonState | button;
			}
		};

		/**
		 * Event handler for mouseup events.
		 * @private
		 */
		var _onMouseUp = function(e){
			e = e || window.event;

			if (e.buttons !== undefined){
				_buttonState = e.buttons;
			} else {
				// Get equivalent e.buttons value.
				var button = _buttonValues[e.button];

				// Store button state as a bit field.
				_buttonState = _buttonState & ~button;
			}
		};

		/**
		 * Event handler for mousemove events.
		 * @private
		 */
		var _onMouseMove = function(e){
			e = e || window.event;

			// Update stored positions
			_cursorPosition.x = e.clientX;
			_cursorPosition.y = e.clientY;
		};

		/**
		 * Event handler for wheel events.
		 * Currently wheel delta is only tracked in the y direction.
		 * @private
		 */
		var _onWheel = function(e){
			e = e || window.event;

			// Add to current delta.
			_wheelDelta += e.deltaY;
		};

		/**
		 * Attaches the necessary event handlers to receive incoming mouse events from the bound element.
		 * Called automatically during construction.
		 * @public
		 */
		this.attach = function(){
			_element.addEventListener('mousedown', _onMouseDown, true);
			_element.addEventListener('mouseup',   _onMouseUp,   true);
			_element.addEventListener('mousemove', _onMouseMove, true);
			_element.addEventListener('wheel',     _onWheel,     true);
		};

		/**
		 * Detaches all event listeners created by this instance. This should be called before discarding
		 * the instance to avoid memory leaks.
		 * @public
		 */
		this.detach = function(){
			_element.removeEventListener('mousedown', _onMouseDown, true);
			_element.removeEventListener('mouseup',   _onMouseUp,   true);
			_element.removeEventListener('mousemove', _onMouseMove, true);
			_element.removeEventListener('wheel',     _onWheel,     true);
		};

		/**
		 * Returns the current state of the mouse.
		 * @public
		 * @returns {thruster.input.MouseState}
		 */
		this.getState = function() {
			return _currentState;
		};

		/**
		 * Updates the internal state.
		 * @public
		 */
		this.update = function() {
			_currentState = new MouseState(_cursorPosition, _buttonState, _wheelDelta, _previousCursorPosition, _previousButtonState);

			_wheelDelta = 0;
			_previousButtonState = _buttonState;
			_previousCursorPosition = _cursorPosition.clone(); // Clone to prevent changes from affecting both properties.
		};

		// Constructor
		this.attach();
		this.update();
	};

	return Mouse;
});<|endoftext|>


<commit_before>define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/WaveShaper"], function(Tone){

	"use strict";

	/**
	 *  @class A simple distortion effect using the waveshaper node
	 *         algorithm from http://stackoverflow.com/a/22313408
	 *
	 *  @extends {Tone.Effect}
	 *  @constructor
	 *  @param {number} distortion the amount of distortion (nominal range of 0-1)
	 *  @example
	 *  var dist = new Tone.Distortion(0.8);
	 */
	Tone.Distortion = function(){

		var options = this.optionsObject(arguments, ["distortion"], Tone.Distortion.defaults);

		Tone.Effect.call(this);

		/**
		 *  @type {Tone.WaveShaper}
		 *  @private
		 */
		this._shaper = new Tone.WaveShaper(4096);

		/**
		 * holds the distortion amount
		 * @type {number}
		 * @private
		 */
		this._distortion = options.distortion;

		this.connectEffect(this._shaper);
		this.distortion = options.distortion;
		this.oversample = options.oversample;
	};

	Tone.extend(Tone.Distortion, Tone.Effect);

	/**
	 *  @static
	 *  @const
	 *  @type {Object}
	 */
	Tone.Distortion.defaults = {
		"distortion" : 0.4,
		"oversample" : "none"
	};

	/**
	 * The amount of distortion. Range between 0-1.
	 * @memberOf Tone.Distortion#
	 * @type {number}
	 * @name distortion
	 */
	Object.defineProperty(Tone.Distortion.prototype, "distortion", {
		get : function(){
			return this._distortion;
		},
		set : function(amount){
			this._distortion = amount;
			var k = amount * 100;
			var deg = Math.PI / 180;
			this._shaper.setMap(function(x){
				if (Math.abs(x) < 0.001){
					//should output 0 when input is 0
					return 0;
				} else {
					return ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) );
				}
			});
		}
	});

	/**
	 * The oversampling of the effect. Can either be "none", "2x" or "4x".
	 * @memberOf Tone.Distortion#
	 * @type {string}
	 * @name oversample
	 */
	Object.defineProperty(Tone.Distortion.prototype, "oversample", {
		get : function(){
			return this._shaper.oversample;
		},
		set : function(oversampling){
			this._shaper.oversample = oversampling;
		}
	});

	/**
	 *  clean up
	 *  @returns {Tone.Distortion} `this`
	 */
	Tone.Distortion.prototype.dispose = function(){
		Tone.Effect.prototype.dispose.call(this);
		this._shaper.dispose();
		this._shaper = null;
		return this;
	};

	return Tone.Distortion;
});<commit_msg>passing options to base class<commit_after>define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/WaveShaper"], function(Tone){

	"use strict";

	/**
	 *  @class A simple distortion effect using the waveshaper node
	 *         algorithm from http://stackoverflow.com/a/22313408
	 *
	 *  @extends {Tone.Effect}
	 *  @constructor
	 *  @param {number} distortion the amount of distortion (nominal range of 0-1)
	 *  @example
	 *  var dist = new Tone.Distortion(0.8);
	 */
	Tone.Distortion = function(){

		var options = this.optionsObject(arguments, ["distortion"], Tone.Distortion.defaults);

		Tone.Effect.call(this, options);

		/**
		 *  @type {Tone.WaveShaper}
		 *  @private
		 */
		this._shaper = new Tone.WaveShaper(4096);

		/**
		 * holds the distortion amount
		 * @type {number}
		 * @private
		 */
		this._distortion = options.distortion;

		this.connectEffect(this._shaper);
		this.distortion = options.distortion;
		this.oversample = options.oversample;
	};

	Tone.extend(Tone.Distortion, Tone.Effect);

	/**
	 *  @static
	 *  @const
	 *  @type {Object}
	 */
	Tone.Distortion.defaults = {
		"distortion" : 0.4,
		"oversample" : "none"
	};

	/**
	 * The amount of distortion. Range between 0-1.
	 * @memberOf Tone.Distortion#
	 * @type {number}
	 * @name distortion
	 */
	Object.defineProperty(Tone.Distortion.prototype, "distortion", {
		get : function(){
			return this._distortion;
		},
		set : function(amount){
			this._distortion = amount;
			var k = amount * 100;
			var deg = Math.PI / 180;
			this._shaper.setMap(function(x){
				if (Math.abs(x) < 0.001){
					//should output 0 when input is 0
					return 0;
				} else {
					return ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) );
				}
			});
		}
	});

	/**
	 * The oversampling of the effect. Can either be "none", "2x" or "4x".
	 * @memberOf Tone.Distortion#
	 * @type {string}
	 * @name oversample
	 */
	Object.defineProperty(Tone.Distortion.prototype, "oversample", {
		get : function(){
			return this._shaper.oversample;
		},
		set : function(oversampling){
			this._shaper.oversample = oversampling;
		}
	});

	/**
	 *  clean up
	 *  @returns {Tone.Distortion} `this`
	 */
	Tone.Distortion.prototype.dispose = function(){
		Tone.Effect.prototype.dispose.call(this);
		this._shaper.dispose();
		this._shaper = null;
		return this;
	};

	return Tone.Distortion;
});<|endoftext|>


<commit_before>/*!
 * yowl
 * Copyright(c) 2016 Brian Brunner
 * MIT Licensed
 */

'use strict';

/**
 * Module Dependencies
 * @private
 */

var _ = require('lodash');
var util = require('util');

/**
 * Context prototype
 */

var context = module.exports = {};

/**
 *
 */

context.get = function get(name, defaultVal) {
  if (name in this) {
    return this[name];
  } else {
    return defaultVal;
  }
};

/**
 * Assign properties to a context with another object
 * @public
 */

context.assign = context.extend = function assign(object) {
  _.assign(this, object);
};

/**
 * Merge a context with another object
 * @public
 */

context.merge = function merge(object) {
  _.merge(this, object);
};

/**
 * Dump a context to an object
 *
 * This function returns a cleaned version of the context.
 * The cleaned object does not have any functions or internal
 * values. This is useful for persisting a context so it can
 * be reoladed later.
 *
 * @public
 */

context.dumpSession = function dumpSession() {
  return JSON.parse(this.jsonDump());
};

/**
 * Dump a context to a JSON string
 * @private
 */

context.jsonDumpSession = function jsonDumpSession() {

  if (!this.session) {
    throw new Error("There is no session to dump");
  }

  var cache = [];

  // remove event temporarily
  var event = this.event;
  delete this.event;

  var contextJSON = JSON.stringify(this.session, function(key, value) {
    if (key.charAt(0) != '_') {
      if (typeof value === "object" && value !== null) {
        if (cache.indexOf(value) !== -1) {
          return;
        }
        cache.push(value);
      }
      return value;
    }
  });

  // add event back
  this.event = event;

  return contextJSON;
};
<commit_msg>Modified assign/merge to work with sessions<commit_after>/*!
 * yowl
 * Copyright(c) 2016 Brian Brunner
 * MIT Licensed
 */

'use strict';

/**
 * Module Dependencies
 * @private
 */

var _ = require('lodash');
var util = require('util');

/**
 * Context prototype
 */

var context = module.exports = {};

/**
 *
 */

context.get = function get(name, defaultVal) {
  if (name in this) {
    return this[name];
  } else {
    return defaultVal;
  }
};

/**
 * Assign properties to a context with another object
 * @public
 */

context.assignSession = context.extendSession = function assignSession(object) {
  if (!this.session) {
    this.session = object;
  } else {
    _.assign(this.session, object);
  }
};

/**
 * Merge a context with another object
 * @public
 */

context.mergeSession = function mergeSession(object) {
  if (!this.session) {
    this.session = object;
  } else {
    _.merge(this.session, object);
  }
};

/**
 * Dump a context to an object
 *
 * This function returns a cleaned version of the context.
 * The cleaned object does not have any functions or internal
 * values. This is useful for persisting a context so it can
 * be reoladed later.
 *
 * @public
 */

context.dumpSession = function dumpSession() {
  return JSON.parse(this.jsonDump());
};

/**
 * Dump a context to a JSON string
 * @private
 */

context.jsonDumpSession = function jsonDumpSession() {

  if (!this.session) {
    throw new Error("There is no session to dump");
  }

  var cache = [];

  // remove event temporarily
  var event = this.event;
  delete this.event;

  var contextJSON = JSON.stringify(this.session, function(key, value) {
    if (key.charAt(0) != '_') {
      if (typeof value === "object" && value !== null) {
        if (cache.indexOf(value) !== -1) {
          return;
        }
        cache.push(value);
      }
      return value;
    }
  });

  // add event back
  this.event = event;

  return contextJSON;
};
<|endoftext|>


<commit_before>angular.module('fitness.game', [])

// directive for auto-focusing popup input when shown
.directive('focusMe', function($timeout) {
  return {
    link: function(scope, element, attrs) {
      $timeout(function() {
        element[0].focus();
      }, 150);
    }
  };
})
.directive('advanceInputArea', function($timeout, $rootScope) {
	return {
		restrict: 'A',
		link: function(scope, element, attrs) {
			$timeout(function() {
				scope.inputs = document.getElementById('popup-input-container').getElementsByTagName('input');
				scope.index = 0;
				scope.done = false;
				scope.indexStack = [];
				$rootScope.letters = [];
				findInitialInput();

				element.bind('keydown', function(e) {
					 if(e.key == "Backspace" || e.key == "Delete") {
						e.preventDefault();
						findPrevInput();
					} else if(e.key.match(/^[a-z]$/i)) {
						if(!scope.done) {
							e.preventDefault();
							scope.inputs[scope.index].value = e.key;
							$rootScope.letters[scope.index] = e.key;
							findNextInput(scope.index);
						}
					} else {
						console.error("Incorrect key: " + e.key)
						e.preventDefault();
					}
				});
			}, 150);

			function findInitialInput() {
				for(var i = 0; i < scope.inputs.length; i++) {
					if(scope.inputs[i].value == ".") {
						scope.inputs[i].select();
						scope.inputs[i].focus();
						scope.index = i;
						return;
					}
				}
				scope.inputs[scope.index].blur();
				scope.done = true;
			}

			function findPrevInput() {
				if(scope.indexStack.length > 0) {
					var ind = scope.indexStack.shift();
					scope.inputs[ind].value = ".";
					scope.inputs[ind].select();
					scope.inputs[ind].focus();
					scope.index = ind;
				} else {
					console.error("beginning reached!")
				}
			}

			function findNextInput(startIndex) {
				scope.indexStack.unshift(startIndex);
				for(var i = startIndex; i < scope.inputs.length; i++) {
					if(scope.inputs[i].value == ".") {
						scope.inputs[i].select();
						scope.inputs[i].focus();
						scope.index = i;
						return;
					}
				}
				scope.inputs[scope.index].blur();
				scope.done = true;
			}
		}
	}
})
.controller('GameCtrl', function($ionicPopup, $ionicModal, $timeout, $interval, $rootScope, gameService, $scope, $state) {

	console.log('Game Controller loaded');

	var self = this;
	var vowels = ['A', 'E', 'I', 'O', 'U'];
	var numVowels;
	var numCons;
	var vowelsRevealed;
	var consRevealed;
	var vowelStack, consStack, thePhrase, noSpace;
	var thePhrase;

	this.init = function(){
		console.log('init game function');
		var obj = {} ;

		self.loadGame();
	};

	this.showPopup = function() {
		$scope.copy = _.cloneDeep(self.phrase);
		$scope.data = self.stringifyViewModel(self.phrase);
		console.info(thePhrase)
		var guessPopup = $ionicPopup.show({
			templateUrl: 'pages/game/guess-popup.html',
			title: 'Enter Phrase',
			scope: $scope,
			buttons: [
				{
					text: 'Cancel',
					type: 'button-dark'
				},
				{
					text: 'Solve The Puzzle',
					type: 'button-positive',
					onTap: function(e) {
						if(!self.guess) {
							self.badGuess();
						} else {
							var actual = self.guess.toUpperCase().trim();
							var expected = thePhrase.toUpperCase().trim();
							console.info(actual)
							console.info(expected)
							if(actual && actual === expected) {
								self.goodGuess();
							} else {
								self.badGuess();
							}
						}
					}
				}
			]
		});
	};

	this.stringifyViewModel = function(viewModel) {
		var str = "";
		for(var a = 0; a < viewModel.length; a++) {
			for(var b = 0; b < viewModel[a].length; b++) {
				if(viewModel[a][b].revealed) {
					str += viewModel[a][b].letter;
				} else {
					str += ".";
				}
			}
			str += "  ";
		}
		return str;
	};

	this.goodGuess = function() {
		console.log("good guess!")
		$ionicPopup.alert({
	     	title: 'You are correct!',
	    	 template: 'Great Job! Play another.'
	   });
		self.revealAll();
		self.closeGame();
		gameService.addScore();

	};

	this.badGuess = function() {
		self.guess = "";
		$ionicPopup.alert({
	     title: 'Wrong Guess!',
	     template: 'Penalty points were added to your goals. Try again!'
	   });

	   gameService.addPenalty();
	};

	this.timeExpired = function(){
		self.guess = "";
			$ionicPopup.alert({
	  		   title: 'Time Expired!',
	  		   template: 'Uh no! You ran out of time. Better luck next time!'
	   });

	   $state.go('app.home');

	}

	this.initModal = function(template) {
		$ionicModal.fromTemplateUrl(template, {
			scope: $scope,
			animation: 'slide-in-up'
		}).then(function(modal) {
			self.modal = modal;
			self.modal.show();
		});
	};

	this.reveal = function(letterType) {
		var curr;
		if(letterType === "V" && vowelStack.length > 1) {
			curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				curr.revealed = true;
				vowelsRevealed++;
			}
		} else if(letterType === "C" && consStack.length > 1) {
			curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				curr.revealed = true;
				consRevealed++;
			}
		} else {
			console.error("something went wrong")
			// either we have a bad lettertype or both stacks are empty
		}
	};

	// reveals the entire phrase
	this.revealAll = function() {
		vowelStack.forEach(function(d) {
			d.model = d.letter;
		});

		consStack.forEach(function(d) {
			d.model = d.letter;
		});
	}

	// takes the number of vowels and consonants that should be revealed and reveals them
	this.catchUp = function(vowels, consonants) {
		console.log('catch up to ' + vowels, consonants);
		if(vowels < 0 || consonants < 0) {
			return;
		}

		for(var a = 0; a < vowels; a++) {
			self.reveal("V");
		}

		for(var b = 0; b < consonants; b++) {
			self.reveal("C");
		}

		self.saveGame();
	};

	this.saveGame = function() {
		console.info("game saved!")

		if (!gameService.activeGame)
			return null ;

		var state = {
			numVowels: numVowels,
			numCons: numCons,
			vowelsRevealed: vowelsRevealed,
			consRevealed: consRevealed,
			vowelStack: vowelStack,
			consStack: consStack,
			viewModel: self.phrase,
			thePhrase: thePhrase,
			noSpace: noSpace,
			hint: self.hint
		};

		return localforage.setItem("gameState", JSON.stringify(state));
	};

	this.loadGame = function() {
		console.info("game loaded!")
		localforage.getItem("gameState").then(function(savedState) {

			if (savedState){

				savedState = JSON.parse(savedState);

				numVowels = savedState.numVowels;
				numCons = savedState.numCons;
				vowelsRevealed = savedState.vowelsRevealed;
				consRevealed = savedState.consRevealed;
				vowelStack = savedState.vowelStack;
				consStack = savedState.consStack;
				self.phrase = savedState.viewModel;
				thePhrase = savedState.thePhrase;
				noSpace = savedState.noSpace;
				self.hint = savedState.hint;
			}
			else{
				obj = gameService.startGame();
				self.guess = "" ;

				gameService.activeGame = obj ;
				gameService.activeGameFlag = true ;

				numVowels = obj.numVowels;
				numCons = obj.numCons;
				vowelsRevealed = 0;
				consRevealed = 0;
				vowelStack = obj.vowelStack;
				consStack = obj.consStack;
				self.phrase = obj.viewModel;
				thePhrase = obj.phrase;
				noSpace = obj.noSpace;
				self.hint = obj.hint;

				self.saveGame();

			}
			gameService.activeGame = true;
		});
	};

	this.testReveal = function() {
		if(vowelStack.length > 0) {
			self.reveal("V");
		} else {
			self.reveal("C");
		}
	}

	this.drawFriendly = function() {

		console.log('goal steps ' + self.fitbitData.goals.steps);

		jQuery("#friend-progress").empty();

			self.friendlyProgress = jQuery("#friend-progress").radialMultiProgress("init", {
				'fill': 25,
				'font-size': 14,
				'size': 100,
				'data': [
					{'color': "#2DB1E4", 'range': [0, self.fitbitData.goals.steps]}, // steps
					{'color': "#9CCA13", 'range': [0, self.fitbitData.goals.floors]}, // floors
					{'color': "#cf2583", 'range': [0, 24]} // time
				]
			});

		var dh, dm, ds;
	//	setInterval(function() {
			console.log('in the draw circle func.');

			var date = new Date(),
	        h = date.getHours(),
	        m = date.getMinutes(),
	        s = date.getSeconds();

			self.fitbitData.time = gameService.updateTime(h);

			console.log('hours is ' + h);

	    	self.friendlyProgress.radialMultiProgress("to", {
	      		"index": 0, 'perc': self.fitbitData.steps, 'time': 10
	      	});
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 1, 'perc': self.fitbitData.floors, 'time': 10
	    	});
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 2, 'perc': h, 'time': 10
	    	});
	  //  }
//	  }, 1000);
	};

	$scope.$on('$ionicView.leave', function(e) {
		self.saveGame();
	});

	$scope.$on('$ionicView.enter', function(e) {

		self.fitbitData = {} ;
		self.fitbitData.goals = {} ;

		self.fitbitData = gameService.data ;

		console.log('steps in game.js: ' + self.fitbitData.steps);

		console.log('getting access token from storage');
		localforage.getItem('fitbitToken').then(function(token){
			if (!token)
				gameService.reload();

			gameService.fitbitToken = token;
			console.log('saving token to game service: ' + token);
			gameService.getFitbitData();
			$timeout( self.computeGameLogic, 4000);
		});

		self.init();

		$interval.cancel(self.queryInterval);

		self.queryInterval = $interval( function(){ self.refreshData(); } , 260000);

		$timeout( self.drawFriendly, 1000);
	});

	this.refreshData = function(){

		gameService.getFitbitData();
		$timeout( self.computeGameLogic, 2000);
		$timeout( self.drawFriendly, 750);
		$scope.$broadcast('scroll.refreshComplete');
	}

	this.computeGameLogic = function(){
		var vowelWorth = parseInt( self.fitbitData.goals.floors /  numVowels);
		var consWorth = parseInt(self.fitbitData.goals.steps /  numCons) ;

		if (consWorth < 200) // min steps required
			consWorth = 200 ;

		if (vowelWorth < 1) // min floors required
			vowelWorth = 1 ;

		var vowelCount = parseInt(self.fitbitData.floors / vowelWorth ) - vowelsRevealed;
		var consCount = parseInt(self.fitbitData.steps / consWorth ) - consRevealed;

		if (vowelCount < 0)
			vowelCount = 0;
		if (consCount < 0)
			consCount = 0 ;

		self.catchUp( vowelCount, consCount );
	}

	this.openGiveUpAlert = function() {
		$ionicPopup.confirm({
	     title: 'Aww You\'re Ready To Give Up Already?',
	     template: 'Are you sure you want to give up on this puzzle? All progress will be lost.',
		 okText: 'Give Up'
	   }).then(function(res) {
	   	if(res) {
	   		self.closeGame();
	   	} else {
	   		// do nothing
	   	}
	   });
	};

	this.closeGame = function(){
		// needs a prompt

		localforage.removeItem("gameState");

		gameService.activeGame = false;
		gameService.clearGame();
		$state.transitionTo('app.home');
	}
});
<commit_msg>fixed vowel worth<commit_after>angular.module('fitness.game', [])

// directive for auto-focusing popup input when shown
.directive('focusMe', function($timeout) {
  return {
    link: function(scope, element, attrs) {
      $timeout(function() {
        element[0].focus();
      }, 150);
    }
  };
})
.directive('advanceInputArea', function($timeout, $rootScope) {
	return {
		restrict: 'A',
		link: function(scope, element, attrs) {
			$timeout(function() {
				scope.inputs = document.getElementById('popup-input-container').getElementsByTagName('input');
				scope.index = 0;
				scope.done = false;
				scope.indexStack = [];
				$rootScope.letters = [];
				findInitialInput();

				element.bind('keydown', function(e) {
					 if(e.key == "Backspace" || e.key == "Delete") {
						e.preventDefault();
						findPrevInput();
					} else if(e.key.match(/^[a-z]$/i)) {
						if(!scope.done) {
							e.preventDefault();
							scope.inputs[scope.index].value = e.key;
							$rootScope.letters[scope.index] = e.key;
							findNextInput(scope.index);
						}
					} else {
						console.error("Incorrect key: " + e.key)
						e.preventDefault();
					}
				});
			}, 150);

			function findInitialInput() {
				for(var i = 0; i < scope.inputs.length; i++) {
					if(scope.inputs[i].value == ".") {
						scope.inputs[i].select();
						scope.inputs[i].focus();
						scope.index = i;
						return;
					}
				}
				scope.inputs[scope.index].blur();
				scope.done = true;
			}

			function findPrevInput() {
				if(scope.indexStack.length > 0) {
					var ind = scope.indexStack.shift();
					scope.inputs[ind].value = ".";
					scope.inputs[ind].select();
					scope.inputs[ind].focus();
					scope.index = ind;
				} else {
					console.error("beginning reached!")
				}
			}

			function findNextInput(startIndex) {
				scope.indexStack.unshift(startIndex);
				for(var i = startIndex; i < scope.inputs.length; i++) {
					if(scope.inputs[i].value == ".") {
						scope.inputs[i].select();
						scope.inputs[i].focus();
						scope.index = i;
						return;
					}
				}
				scope.inputs[scope.index].blur();
				scope.done = true;
			}
		}
	}
})
.controller('GameCtrl', function($ionicPopup, $ionicModal, $timeout, $interval, $rootScope, gameService, $scope, $state) {

	console.log('Game Controller loaded');

	var self = this;
	var vowels = ['A', 'E', 'I', 'O', 'U'];
	var numVowels;
	var numCons;
	var vowelsRevealed;
	var consRevealed;
	var vowelStack, consStack, thePhrase, noSpace;
	var thePhrase;

	this.init = function(){
		console.log('init game function');
		var obj = {} ;

		self.loadGame();
	};

	this.showPopup = function() {
		$scope.copy = _.cloneDeep(self.phrase);
		$scope.data = self.stringifyViewModel(self.phrase);
		console.info(thePhrase)
		var guessPopup = $ionicPopup.show({
			templateUrl: 'pages/game/guess-popup.html',
			title: 'Enter Phrase',
			scope: $scope,
			buttons: [
				{
					text: 'Cancel',
					type: 'button-dark'
				},
				{
					text: 'Solve The Puzzle',
					type: 'button-positive',
					onTap: function(e) {
						if(!self.guess) {
							self.badGuess();
						} else {
							var actual = self.guess.toUpperCase().trim();
							var expected = thePhrase.toUpperCase().trim();
							console.info(actual)
							console.info(expected)
							if(actual && actual === expected) {
								self.goodGuess();
							} else {
								self.badGuess();
							}
						}
					}
				}
			]
		});
	};

	this.stringifyViewModel = function(viewModel) {
		var str = "";
		for(var a = 0; a < viewModel.length; a++) {
			for(var b = 0; b < viewModel[a].length; b++) {
				if(viewModel[a][b].revealed) {
					str += viewModel[a][b].letter;
				} else {
					str += ".";
				}
			}
			str += "  ";
		}
		return str;
	};

	this.goodGuess = function() {
		console.log("good guess!")
		$ionicPopup.alert({
	     	title: 'You are correct!',
	    	 template: 'Great Job! Play another.'
	   });
		self.revealAll();
		self.closeGame();
		gameService.addScore();

	};

	this.badGuess = function() {
		self.guess = "";
		$ionicPopup.alert({
	     title: 'Wrong Guess!',
	     template: 'Penalty points were added to your goals. Try again!'
	   });

	   gameService.addPenalty();
	};

	this.timeExpired = function(){
		self.guess = "";
			$ionicPopup.alert({
	  		   title: 'Time Expired!',
	  		   template: 'Uh no! You ran out of time. Better luck next time!'
	   });

	   $state.go('app.home');

	}

	this.initModal = function(template) {
		$ionicModal.fromTemplateUrl(template, {
			scope: $scope,
			animation: 'slide-in-up'
		}).then(function(modal) {
			self.modal = modal;
			self.modal.show();
		});
	};

	this.reveal = function(letterType) {
		var curr;
		if(letterType === "V" && vowelStack.length > 1) {
			curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				curr.revealed = true;
				vowelsRevealed++;
			}
		} else if(letterType === "C" && consStack.length > 1) {
			curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				curr.revealed = true;
				consRevealed++;
			}
		} else {
			console.error("something went wrong")
			// either we have a bad lettertype or both stacks are empty
		}
	};

	// reveals the entire phrase
	this.revealAll = function() {
		vowelStack.forEach(function(d) {
			d.model = d.letter;
		});

		consStack.forEach(function(d) {
			d.model = d.letter;
		});
	}

	// takes the number of vowels and consonants that should be revealed and reveals them
	this.catchUp = function(vowels, consonants) {
		console.log('catch up to ' + vowels, consonants);
		if(vowels < 0 || consonants < 0) {
			return;
		}

		for(var a = 0; a < vowels; a++) {
			self.reveal("V");
		}

		for(var b = 0; b < consonants; b++) {
			self.reveal("C");
		}

		self.saveGame();
	};

	this.saveGame = function() {
		console.info("game saved!")

		if (!gameService.activeGame)
			return null ;

		var state = {
			numVowels: numVowels,
			numCons: numCons,
			vowelsRevealed: vowelsRevealed,
			consRevealed: consRevealed,
			vowelStack: vowelStack,
			consStack: consStack,
			viewModel: self.phrase,
			thePhrase: thePhrase,
			noSpace: noSpace,
			hint: self.hint
		};

		return localforage.setItem("gameState", JSON.stringify(state));
	};

	this.loadGame = function() {
		console.info("game loaded!")
		localforage.getItem("gameState").then(function(savedState) {

			if (savedState){

				savedState = JSON.parse(savedState);

				numVowels = savedState.numVowels;
				numCons = savedState.numCons;
				vowelsRevealed = savedState.vowelsRevealed;
				consRevealed = savedState.consRevealed;
				vowelStack = savedState.vowelStack;
				consStack = savedState.consStack;
				self.phrase = savedState.viewModel;
				thePhrase = savedState.thePhrase;
				noSpace = savedState.noSpace;
				self.hint = savedState.hint;
			}
			else{
				obj = gameService.startGame();
				self.guess = "" ;

				gameService.activeGame = obj ;
				gameService.activeGameFlag = true ;

				numVowels = obj.numVowels;
				numCons = obj.numCons;
				vowelsRevealed = 0;
				consRevealed = 0;
				vowelStack = obj.vowelStack;
				consStack = obj.consStack;
				self.phrase = obj.viewModel;
				thePhrase = obj.phrase;
				noSpace = obj.noSpace;
				self.hint = obj.hint;

				self.saveGame();

			}
			gameService.activeGame = true;
		});
	};

	this.testReveal = function() {
		if(vowelStack.length > 0) {
			self.reveal("V");
		} else {
			self.reveal("C");
		}
	}

	this.drawFriendly = function() {

		console.log('goal steps ' + self.fitbitData.goals.steps);

		jQuery("#friend-progress").empty();

			self.friendlyProgress = jQuery("#friend-progress").radialMultiProgress("init", {
				'fill': 25,
				'font-size': 14,
				'size': 100,
				'data': [
					{'color': "#2DB1E4", 'range': [0, self.fitbitData.goals.steps]}, // steps
					{'color': "#9CCA13", 'range': [0, self.fitbitData.goals.floors]}, // floors
					{'color': "#cf2583", 'range': [0, 24]} // time
				]
			});

		var dh, dm, ds;
	//	setInterval(function() {
			console.log('in the draw circle func.');

			var date = new Date(),
	        h = date.getHours(),
	        m = date.getMinutes(),
	        s = date.getSeconds();

			self.fitbitData.time = gameService.updateTime(h);

			console.log('hours is ' + h);

	    	self.friendlyProgress.radialMultiProgress("to", {
	      		"index": 0, 'perc': self.fitbitData.steps, 'time': 10
	      	});
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 1, 'perc': self.fitbitData.floors, 'time': 10
	    	});
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 2, 'perc': h, 'time': 10
	    	});
	  //  }
//	  }, 1000);
	};

	$scope.$on('$ionicView.leave', function(e) {
		self.saveGame();
	});

	$scope.$on('$ionicView.enter', function(e) {

		self.fitbitData = {} ;
		self.fitbitData.goals = {} ;

		self.fitbitData = gameService.data ;

		console.log('steps in game.js: ' + self.fitbitData.steps);

		console.log('getting access token from storage');
		localforage.getItem('fitbitToken').then(function(token){
			if (!token)
				gameService.reload();

			gameService.fitbitToken = token;
			console.log('saving token to game service: ' + token);
			gameService.getFitbitData();
			$timeout( self.computeGameLogic, 4000);
		});

		self.init();

		$interval.cancel(self.queryInterval);

		self.queryInterval = $interval( function(){ self.refreshData(); } , 260000);

		$timeout( self.drawFriendly, 1000);
	});

	this.refreshData = function(){

		gameService.getFitbitData();
		$timeout( self.computeGameLogic, 2000);
		$timeout( self.drawFriendly, 750);
		$scope.$broadcast('scroll.refreshComplete');
	}

	this.computeGameLogic = function(){
		var vowelWorth = parseInt( self.fitbitData.goalFloors /  numVowels);
		var consWorth = parseInt(self.fitbitData.goalSteps /  numCons) ;

		if (consWorth < 200) // min steps required
			consWorth = 200 ;

		if (vowelWorth < 1) // min floors required
			vowelWorth = 1 ;

		var vowelCount = parseInt(self.fitbitData.floors / vowelWorth ) - vowelsRevealed;
		var consCount = parseInt(self.fitbitData.steps / consWorth ) - consRevealed;

		if (vowelCount < 0)
			vowelCount = 0;
		if (consCount < 0)
			consCount = 0 ;

		self.catchUp( vowelCount, consCount );
	}

	this.openGiveUpAlert = function() {
		$ionicPopup.confirm({
	     title: 'Aww You\'re Ready To Give Up Already?',
	     template: 'Are you sure you want to give up on this puzzle? All progress will be lost.',
		 okText: 'Give Up'
	   }).then(function(res) {
	   	if(res) {
	   		self.closeGame();
	   	} else {
	   		// do nothing
	   	}
	   });
	};

	this.closeGame = function(){
		// needs a prompt

		localforage.removeItem("gameState");

		gameService.activeGame = false;
		gameService.clearGame();
		$state.transitionTo('app.home');
	}
});
<|endoftext|>


<commit_before>describe('home page', function() {
  var input = element(by.id('searchBox'));
  var submitButton = element(by.id('searchBtn'));
  var webPill = element(by.id('web'));
  var imagePill = element(by.id('images'));
  var newsPill = element(by.id('news'));

  browser.driver.get('file:///Volumes/Nimbus/Users/racan/Projects/search-demo/index.html');
  browser.manage().timeouts().implicitlyWait(500);

  it('should have an input box to allow user to enter query', function() {
    expect(input.isPresent()).toBe(true);
  });

  it('should have a submit button', function() {
    expect(submitButton.isPresent()).toBe(true);
  });

  it('should have selectors to switch between web/image/news searches', function() {
    submitButton.click();
    expect(webPill.isPresent()).toBe(true);
    expect(imagePill.isPresent()).toBe(true);
    expect(newsPill.isPresent()).toBe(true);
  });

  it('should not display results partial if no search string was provided', function () {
    submitButton.click();
    expect(webPill.isDisplayed()).toBe(false);
    expect(imagePill.isDisplayed()).toBe(false);
    expect(newsPill.isDisplayed()).toBe(false);
  });

  it('should default to web search for the initial query', function () {
    input.sendKeys('wrx sti');
    submitButton.click();
    browser.waitForAngular();
    expect(webPill.isDisplayed()).toBe(true);
    expect(webPill.getAttribute('class')).toBe('active');
    expect(imagePill.getAttribute('class')).toBe('');
    expect(newsPill.getAttribute('class')).toBe('');
  });

  it('should limit the initial search list to 50 records', function() {
    var resultList = element.all(by.repeater('result in vm.results'));
    expect(resultList.count()).toEqual(50);
  });
});<commit_msg>re-organize e2e spec and add tests for search selectors<commit_after>describe('home page', function() {
  var input = element(by.id('searchBox'));
  var submitButton = element(by.id('searchBtn'));
  var webPill = element(by.id('web'));
  var imagePill = element(by.id('images'));
  var newsPill = element(by.id('news'));

  browser.driver.get('file:///Volumes/Nimbus/Users/racan/Projects/search-demo/index.html');
  browser.manage().timeouts().implicitlyWait(500);

  describe('basic features', function () {

    beforeEach(function () {
      input.clear();
    });

    it('should have an input box to allow user to enter query', function() {
      expect(input.isPresent()).toBe(true);
    });

    it('should have a submit button', function() {
      expect(submitButton.isPresent()).toBe(true);
    });

    it('should have selectors to switch between web/image/news searches', function() {
      submitButton.click();
      expect(webPill.isPresent()).toBe(true);
      expect(imagePill.isPresent()).toBe(true);
      expect(newsPill.isPresent()).toBe(true);
    });

    it('should not display results partial if no search string was provided', function () {
      submitButton.click();
      expect(webPill.isDisplayed()).toBe(false);
      expect(imagePill.isDisplayed()).toBe(false);
      expect(newsPill.isDisplayed()).toBe(false);
    });

    it('should default to web search for the initial query', function () {
      input.sendKeys('wrx sti');
      submitButton.click();
      browser.waitForAngular();
      expect(webPill.isDisplayed()).toBe(true);
      expect(webPill.getAttribute('class')).toBe('active');
      expect(imagePill.getAttribute('class')).toBe('');
      expect(newsPill.getAttribute('class')).toBe('');
    });

  });

  describe('search operation selectors', function () {

    input.clear();
    input.sendKeys('2015 ford shelby gt350');
    submitButton.click();
    browser.manage().timeouts().implicitlyWait(500);

    it('should mark the web pill active if performing a web search', function () {
      webPill.click();
      browser.waitForAngular();
      expect(webPill.getAttribute('class')).toBe('active');
      expect(imagePill.getAttribute('class')).toBe('');
      expect(newsPill.getAttribute('class')).toBe('');
    });

    it('should mark the image pill active if performing a image search', function () {
      imagePill.click();
      browser.waitForAngular();
      expect(webPill.getAttribute('class')).toBe('');
      expect(imagePill.getAttribute('class')).toBe('active');
      expect(newsPill.getAttribute('class')).toBe('');
    });

    it('should mark the news pill active if performing a news search', function () {
      newsPill.click();
      browser.waitForAngular();
      expect(webPill.getAttribute('class')).toBe('');
      expect(imagePill.getAttribute('class')).toBe('');
      expect(newsPill.getAttribute('class')).toBe('active');
    });

  });

  describe('search results', function () {

    it('should limit the initial search list to 25 records', function() {
      input.clear();
      input.sendKeys('windows iot');
      submitButton.click();
      browser.manage().timeouts().implicitlyWait(2000);
      var resultList = element.all(by.repeater('result in vm.results'));
      expect(resultList.count()).toEqual(25);
    });

  });
});<|endoftext|>


<commit_before>import binary from 'test/binary';

import Shape from '../Shape';

describe('we check Shape class', function () {
  it('should be able to create a Shape', function () {
    let shape = new Shape({ shape: 'circle', filled: false, size: 5 });
    let mask = shape.getMask();
    expect(mask.data).toEqual(
      binary`
          00100
          01010
          10001
          01010
          00100
      `
    );
  });

  it('should yield a cross', function () {
    let shape = new Shape();

    expect(shape.getPoints()).toEqual([
      [0, -2],
      [0, -1],
      [-2, 0],
      [-1, 0],
      [0, 0],
      [1, 0],
      [2, 0],
      [0, 1],
      [0, 2]
    ]);
  });

  it('should yield the right smallCross', function () {
    let shape = new Shape({ kind: 'smallCross' });

    expect(Array.from(shape.matrix)).toEqual([
      [0, 1, 0],
      [1, 1, 1],
      [0, 1, 0]
    ]);
  });

  it('should yield the right mask', function () {
    let shape = new Shape({ kind: 'smallCross' });
    let img = shape.getMask();
    expect(Array.from(img.data)).toEqual([93, 0]);
  });

  it('should yield the right cross', function () {
    let shape = new Shape({ kind: 'cross' });

    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0],
      [1, 1, 1, 1, 1],
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right square', function () {
    let shape = new Shape({ shape: 'square', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'square', width: 5, height: 3 });
    expect(Array.from(shape2.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right empty square', function () {
    let shape = new Shape({ shape: 'square', size: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'square', width: 5, height: 3, filled: false });
    expect(Array.from(shape2.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right triangle', function () {
    let shape = new Shape({ shape: 'triangle', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'triangle', width: 5, height: 3 });
    expect(Array.from(shape2.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right circle', function () {
    let shape = new Shape({ shape: 'circle', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1],
      [0, 1, 1, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right circle even size', function () {
    let shape = new Shape({ shape: 'circle', size: 6 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 1, 0, 0],
      [0, 1, 1, 1, 1, 0],
      [1, 1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1, 1],
      [0, 1, 1, 1, 1, 0],
      [0, 0, 1, 1, 0, 0]
    ]);
  });

  it('should yield the right ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 9, height: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 1, 0, 0, 0, 0],
      [0, 1, 1, 1, 1, 1, 1, 1, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1],
      [0, 1, 1, 1, 1, 1, 1, 1, 0],
      [0, 0, 0, 0, 1, 0, 0, 0, 0]
    ]);
  });

  it('should yield the right empty circle', function () {
    let shape = new Shape({ shape: 'circle', size: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 0, 1, 0],
      [1, 0, 0, 0, 1],
      [0, 1, 0, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right empty circle even size', function () {
    let shape = new Shape({ shape: 'circle', size: 6, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 1, 0, 0],
      [0, 1, 0, 0, 1, 0],
      [1, 0, 0, 0, 0, 1],
      [1, 0, 0, 0, 0, 1],
      [0, 1, 0, 0, 1, 0],
      [0, 0, 1, 1, 0, 0]
    ]);
  });

  it('should yield the right empty ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 9, height: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 1, 0, 0, 0, 0],
      [0, 1, 1, 1, 0, 1, 1, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 0, 1],
      [0, 1, 1, 1, 0, 1, 1, 1, 0],
      [0, 0, 0, 0, 1, 0, 0, 0, 0]
    ]);
  });

  it('should yield the right vertical empty ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 5, height: 9, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [1, 0, 0, 0, 1],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right vbig circle', function () {
    let shape = new Shape({ shape: 'ellipse', size: 15, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0],
      [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
      [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
      [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
      [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
    ]);
  });
});

<commit_msg>refactor test shape<commit_after>import binary from 'test/binary';

import Shape from '../Shape';

describe('we check Shape class', function () {
  it('should be able to create a Shape', function () {
    let shape = new Shape({ shape: 'circle', filled: false, size: 5 });
    let mask = shape.getMask();
    expect(mask.data).toEqual(
      binary`
          00100
          01010
          10001
          01010
          00100
      `);
  });

  it('should yield a cross', function () {
    let shape = new Shape();

    expect(shape.getPoints()).toEqual([
      [0, -2],
      [0, -1],
      [-2, 0],
      [-1, 0],
      [0, 0],
      [1, 0],
      [2, 0],
      [0, 1],
      [0, 2]
    ]);
  });

  it('should yield the right smallCross', function () {
    let shape = new Shape({ kind: 'smallCross' });

    expect(Array.from(shape.matrix)).toEqual([
      [0, 1, 0],
      [1, 1, 1],
      [0, 1, 0]
    ]);
  });

  it('should yield the right mask', function () {
    let shape = new Shape({ kind: 'smallCross' });
    let img = shape.getMask();
    expect(Array.from(img.data)).toEqual([93, 0]);
  });

  it('should yield the right cross', function () {
    let shape = new Shape({ kind: 'cross' });

    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0],
      [1, 1, 1, 1, 1],
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right square', function () {
    let shape = new Shape({ shape: 'square', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'square', width: 5, height: 3 });
    expect(Array.from(shape2.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right empty square', function () {
    let shape = new Shape({ shape: 'square', size: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'square', width: 5, height: 3, filled: false });
    expect(Array.from(shape2.matrix)).toEqual([
      [1, 1, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right triangle', function () {
    let shape = new Shape({ shape: 'triangle', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1]
    ]);

    let shape2 = new Shape({ shape: 'triangle', width: 5, height: 3 });
    expect(Array.from(shape2.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1]
    ]);
  });

  it('should yield the right circle', function () {
    let shape = new Shape({ shape: 'circle', size: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 1, 1, 0],
      [1, 1, 1, 1, 1],
      [0, 1, 1, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right circle even size', function () {
    let shape = new Shape({ shape: 'circle', size: 6 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 1, 0, 0],
      [0, 1, 1, 1, 1, 0],
      [1, 1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1, 1],
      [0, 1, 1, 1, 1, 0],
      [0, 0, 1, 1, 0, 0]
    ]);
  });

  it('should yield the right ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 9, height: 5 });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 1, 0, 0, 0, 0],
      [0, 1, 1, 1, 1, 1, 1, 1, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1],
      [0, 1, 1, 1, 1, 1, 1, 1, 0],
      [0, 0, 0, 0, 1, 0, 0, 0, 0]
    ]);
  });

  it('should yield the right empty circle', function () {
    let shape = new Shape({ shape: 'circle', size: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 0, 1, 0],
      [1, 0, 0, 0, 1],
      [0, 1, 0, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right empty circle even size', function () {
    let shape = new Shape({ shape: 'circle', size: 6, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 1, 0, 0],
      [0, 1, 0, 0, 1, 0],
      [1, 0, 0, 0, 0, 1],
      [1, 0, 0, 0, 0, 1],
      [0, 1, 0, 0, 1, 0],
      [0, 0, 1, 1, 0, 0]
    ]);
  });

  it('should yield the right empty ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 9, height: 5, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 1, 0, 0, 0, 0],
      [0, 1, 1, 1, 0, 1, 1, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 0, 1],
      [0, 1, 1, 1, 0, 1, 1, 1, 0],
      [0, 0, 0, 0, 1, 0, 0, 0, 0]
    ]);
  });

  it('should yield the right vertical empty ellipse', function () {
    let shape = new Shape({ shape: 'ellipse', width: 5, height: 9, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 1, 0, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [1, 0, 0, 0, 1],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 1, 0, 1, 0],
      [0, 0, 1, 0, 0]
    ]);
  });

  it('should yield the right vbig circle', function () {
    let shape = new Shape({ shape: 'ellipse', size: 15, filled: false });
    expect(Array.from(shape.matrix)).toEqual([
      [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0],
      [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
      [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
      [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
      [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
      [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
    ]);
  });
});

<|endoftext|>