<commit_before>Package.describe({
    name: "froatsnook:shopify",
    version: "1.3.1",
    summary: "Shopify API Access",
    git: "https://github.com/froatsnook/meteor-shopify",
    documentation: "README.md"
});

Package.onUse(function(api) {
    api.versionsFrom("1.0.3.1");
    api.use("check", ["client", "server"]);
    api.use("http", ["client", "server"]);
    api.use("underscore", ["client", "server"]);
    api.use("webapp", "server");
    api.use("froatsnook:sleep@1.1.0", ["client", "server"]);
    api.addFiles("lib/01-shopify.js", ["client", "server"]);
    api.addFiles("lib/02-APIMethods.js", ["client", "server"]);
    api.addFiles("lib/api.js", ["client", "server"]);
    api.addFiles("lib/Authenticators.js", ["client", "server"]);
    api.addFiles("lib/AuthRedirect.js", "server");
    api.addFiles("lib/DefaultScopes.js", ["client", "server"]);
    api.addFiles("lib/Keyset.js", ["server"]);
    api.addFiles("lib/ProxyAPICache.js", ["server"]);
    api.export("Shopify", ["client", "server"]);
});

Package.onTest(function(api) {
    Npm.depends({ "express": "4.12.2" });

    api.use("tinytest");
    api.use("webapp", "server");
    api.use("tracker");
    api.use("froatsnook:sleep@1.1.0");
    api.use("froatsnook:shopify");
    api.addFiles("test/shopify-api-simulator.js", ["server"]);
    api.addFiles("test/shopify-tests.js");
});

<commit_msg>Bump version to 1.4.0.<commit_after>Package.describe({
    name: "froatsnook:shopify",
    version: "1.4.0",
    summary: "Shopify API Access",
    git: "https://github.com/froatsnook/meteor-shopify",
    documentation: "README.md"
});

Package.onUse(function(api) {
    api.versionsFrom("1.0.3.1");
    api.use("check", ["client", "server"]);
    api.use("http", ["client", "server"]);
    api.use("underscore", ["client", "server"]);
    api.use("webapp", "server");
    api.use("froatsnook:sleep@1.1.0", ["client", "server"]);
    api.addFiles("lib/01-shopify.js", ["client", "server"]);
    api.addFiles("lib/02-APIMethods.js", ["client", "server"]);
    api.addFiles("lib/api.js", ["client", "server"]);
    api.addFiles("lib/Authenticators.js", ["client", "server"]);
    api.addFiles("lib/AuthRedirect.js", "server");
    api.addFiles("lib/DefaultScopes.js", ["client", "server"]);
    api.addFiles("lib/Keyset.js", ["server"]);
    api.addFiles("lib/ProxyAPICache.js", ["server"]);
    api.export("Shopify", ["client", "server"]);
});

Package.onTest(function(api) {
    Npm.depends({ "express": "4.12.2" });

    api.use("tinytest");
    api.use("webapp", "server");
    api.use("tracker");
    api.use("froatsnook:sleep@1.1.0");
    api.use("froatsnook:shopify");
    api.addFiles("test/shopify-api-simulator.js", ["server"]);
    api.addFiles("test/shopify-tests.js");
});

<|endoftext|>


<commit_before>'use strict'

var test = require('tape')

var Compiler = require('../src/app/compiler')
var EventManager = require('../src/lib/eventManager')

test('compiler.compile smoke', function (t) {
  t.plan(1)

  var noop = function () {}
  var getCacheFile = function () { return 'fakeCacheFile' }
  var fakeEditor = {onChangeSetup: noop, clearAnnotations: noop, getValue: noop, setCacheFileContent: noop, getCacheFile: getCacheFile}
  var fakeQueryParams = {get: function () { return {} }}
  var compiler = new Compiler(fakeEditor, fakeQueryParams, null, null, new EventManager())
  compiler.setCompileJSON(noop)
  compiler.compile()
  t.ok(compiler)
})
<commit_msg>Update compiler test for the new API<commit_after>'use strict'

var test = require('tape')

var Compiler = require('../src/app/compiler')

test('compiler.compile smoke', function (t) {
  t.plan(1)

  var noop = function () {}
  var fakeImport = function (url, cb) { cb('Not implemented') }
  var compiler = new Compiler(fakeImport)
  compiler.setCompileJSON(noop)
  compiler.compile({ 'test': '' }, 'test')
  t.ok(compiler)
})
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var labelToProperty = {
  App:          'app',
  Type:         'type',
  Pri:          'pri',
  Priority:     'priority',
  Milestone:    'milestone',
  Mstone:       'milestone',
  M:            'milestone',
  Cr:           'cr',
  Iteration:    'iteration',
  ReleaseBlock: 'releaseBlock',
  OS:           'OS',
  MovedFrom:    'movedFrom'
};

var propertyLabels_ = {};

function isPropertyLabel(l) {
  if ( l in propertyLabels_ ) return propertyLabels_[l];

  var keyValue = l.match(/([^\-]*)\-(.*)/) || l.match(/(\D*)(.*)/);
  if ( keyValue ) {
    var key   = labelToProperty[keyValue[1]];
    var value = keyValue[2];

    if ( key ) {
      var kv = [key, value.intern()];
      propertyLabels_[l] = kv;
      return kv;
    }
  }

  return propertyLabels_[l] = false;
}

var QIssue = FOAM({
    model_: 'Model',
    extendsModel: 'GeneratedQIssue',

    name: 'QIssue',

    tableProperties: [
      'starred',
      'id',
//      'app',
      'priority',
      'milestone',
      'iteration',
      'releaseBlock',
      'cr',
      'status',
      'owner',
      'summary',
      'OS',
      'modified'
    ],

    ids: ['id'],

    properties: [
        {
          model_: 'IntProperty',
          name: 'id',
          shortName: 'i',
          label: 'ID',
          required: true,
          tableWidth: '48px',
          tableFormatter: function(value, row, table) {
            var id = table.nextID();

            table.on('mouseover', function(e) {
              table.browser.preview(e, value);
            }, id);

            return '
' + value + '
';
          }
        },
        {
          name: 'author',
          preSet: function(_, a) { return a.intern(); },
          aliases: ['reporter']
        },
        {
          model_: 'StringProperty',
          name: 'priority',
          shortName: 'p',
          aliases: ['pr', 'prior'],
          tableLabel: 'Priority',
          tableWidth: '60px',
          compareProperty: function(p1, p2) {
            var priorities = ['Low', 'Medium', 'High', 'Critical'];
            var r = priorities.indexOf(p1) - priorities.indexOf(p2);
            return r === 0 ? 0 : r < 0 ? -1 : 1;
          },
          required: true
        },
        {
          model_: 'IntProperty',
          name: 'pri',
          tableLabel: 'Pri',
          tableWidth: '60px'
        },
        {
          model_: 'StringProperty',
          name: 'app',
          tableWidth: '70px'
        },
        {
          model_: 'StringProperty',
          name: 'type',
          tableWidth: '70px'
        },
        {
            name: 'milestone',
            shortName: 'm',
            aliases: ['mstone'],
            tableLabel: 'M',
            type: 'Int',
            tableWidth: '70px',
            defaultValue: ''
        },
        {
            name: 'iteration',
            shortName: 'it',
            aliases: ['iter'],
            type: 'String',
            tableWidth: '69px'
        },
        {
            name: 'releaseBlock',
            shortName: 'rb',
            aliases: ['rBlock', 'release'],
            type: 'String',
            tableWidth: '103px',
            defaultValue: ''
        },
        {
            name: 'cr',
            shortName: 'c',
            aliases: ['cat', 'cr'],
            label: 'Cr',
            tableWidth: '87px',
            type: 'String',
            defaultValue: ''
        },
        {
            name: 'status',
            shortName: 's',
            aliases: ['stat'],
            type: 'String',
            tableWidth: '58px',
            defaultValue: ''
        },
        {
          name: 'cc',
          preSet: function(_, a) { return a.intern(); },
          autocompleter: 'PersonCompleter'
        },
        {
            name: 'owner',
            shortName: 'o',
            tableWidth: '181px',
            type: 'String',
            autocompleter: 'PersonCompleter',
            preSet: function(_, a) { return a.intern(); }
        },
        {
          name: 'summary',
          shortName: 'su',
          label: 'Summary',
          type: 'String',
          tableWidth: '350px',
//          tableWidth: '100%'
          tableFormatter: function(value, row, tableView) {
            return tableView.strToHTML(value);
          }
        },
        {
          name: 'summaryPlusLabels',
          label: 'Summary + Labels',
          tableLabel: 'Summary + Labels',
          type: 'String',
          tableWidth: '100%',
          tableFormatter: function(value, row, tableView) {
            return tableView.strToHTML(row.summary) +
              QIssue.LABELS.tableFormatter(row.labels, row);
          }
        },
        {
            name: 'OS',
            tableWidth: '61px',
            type: 'String'
        },
        {
          model_: 'BooleanProperty',
          name: 'blocked',
          tableWidth: '20px',
          getter: function() { return !! this.blockedOn.length; }
        },
        {
         model_: 'DateTimeProperty',
         name: 'modified',
         shortName: 'mod',
         mode: 'read-write',
         required: true,
         tableWidth: '100px',
         factory: function() { return new Date(); }
      },
      {
        name: 'updated',
        hidden: true,
        setter: function(v) { this.modified = v; }
      },
      {
         model_: 'BooleanProperty',
         name: 'starred',
         tableLabel: '',
         tableWidth: '18px',
         tableFormatter: function(val, obj, tableView) {
           var view = CSSImageBooleanView.create({
             className: 'star-image',
             data: obj.starred
           });

           view.data$.addListener(function() {
             var tmp = obj.clone();
             tmp.starred = view.data;
             tableView.browser.IssueDAO.put(tmp);
           });

           tableView.addChild(view);
           return view.toHTML();
         },
         view: { model_: 'CSSImageBooleanView', className: 'star-image' },
         help: 'Whether the authenticated user has starred this issue.'
      },
      {
         model_: 'IntProperty',
         name: 'stars',
         tableWidth: '20px',
         help: 'Number of stars this issue has.'
      },
      {
        name: 'labels',
        shortName: 'l',
        aliases: ['label'],
        type: 'String',
        view: 'QIssueLabelsView',
        tableFormatter: function(a, row) {
          var s = '';
          for ( var i = 0 ; i < a.length ; i++ ) {
            s += ' ' + a[i] + '';
          }
          return s;
        },
        postSet: function(_, a) {
          for ( var i = 0 ; i < a.length ; i++ ) {
            var kv = isPropertyLabel(a[i]);
            if ( kv ) {
              this[kv[0]] = kv[1];
              // Don't remove from labels because then label:X searches don't work.
              // a.splice(i,1);
              // i--;
            } else {
              a[i] = a[i].intern();
            }
          }
        }
      },
      {
        model_: 'StringArrayProperty',
        name: 'movedFrom',
        tableWidth: '100px',
        preSet: function(_, v) {
          if ( Array.isArray(v) ) return v;
          if ( ! v ) return undefined;

          return (this.movedFrom || []).binaryInsert(v.charAt(0) == 'M' ? parseInt(v.substring(1)) : parseInt(v));
        },
        compareProperty: function(o1, o2) {
          o1 = o1.length ? o1[0] : 0;
          o2 = o2.length ? o2[0] : 0;
          return o1 === o2 ? 0 : o1 > o2 ? 1 : -1;
        }
      },
      {
        name: 'movedTo',
        preSet: function(_, v) {
          // TODO: looks like we're getting bad values, investigate and fix
          /*
          if ( v.length ) console.log('movedTo: ', v);
          return v;
          */
          return [];
        }
      }
    ],

    methods: {
      isOpen: function() {
        return !! ({
          'New':       true,
          'Accepted':  true,
          'Started':   true,
          'Untriaged': true
        }[this.status]);
      },
      writeActions: function(other, out) {
        var diff = this.diff(other);
        delete diff.starred;

        if ( Object.keys(diff).length == 0 ) return;

        function convertArray(key) {
          if ( ! diff[key] ) {
            diff[key] = [];
            return;
          }

          var delta = diff[key].added;
          for ( var i = 0; i < diff[key].removed.length; i++ )
            delta.push("-" + diff[key].removed[i]);
          diff[key] = delta;
        }

        convertArray('blockedOn');
        convertArray('blocking');
        convertArray('cc');
        convertArray('labels');

        function propToLabel(prop, label) {
          if ( diff[prop] ) {
            diff.labels = diff.labels.concat(
              '-' + label + '-' + other[prop],
              label + '-' + diff[prop]);
            delete diff[prop];
          }
        }

        propToLabel('priority', 'Priority');
        propToLabel('app', 'App');
        propToLabel('milestone', 'Milestone');
        propToLabel('cr', 'Cr');
        propToLabel('iteration', 'Iteration');
        propToLabel('releaseBlock', 'ReleaseBlock');
        propToLabel('OS', 'OS');

        var comment = QIssueComment.create({
          issueId: this.id,
          updates: QIssueCommentUpdate.create(diff)
        });

        out(comment);
      },
      newComment: function() {
        return QIssueComment.create({
          issueId: this.id,
          updates: QIssueCommentUpdate.create({
            labels: this.labels.clone(),
            owner: this.owner,
            blockedOn: this.blockedOn.clone(),
            blocking: this.blocking.clone(),
            cc: this.cc.clone(),
            mergedInto: this.mergedInto,
            status: this.status,
            summary: this.summary
          })
        })
      }
    }
});

QIssue.getPrototype();

QIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
});

GeneratedQIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
  if ( ! p["tableWidth"] ) {
    p["tableWidth"] = '80px;'
  }
});

QIssue.LABELS.toMQL = function() { return 'label'; };
<commit_msg>Psedo-property labels no longe appear in the labels column.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var labelToProperty = {
  App:          'app',
  Type:         'type',
  Pri:          'pri',
  Priority:     'priority',
  Milestone:    'milestone',
  Mstone:       'milestone',
  M:            'milestone',
  Cr:           'cr',
  Iteration:    'iteration',
  ReleaseBlock: 'releaseBlock',
  OS:           'OS',
  MovedFrom:    'movedFrom'
};

var propertyLabels_ = {};

function isPropertyLabel(l) {
  if ( l in propertyLabels_ ) return propertyLabels_[l];

  var keyValue = l.match(/([^\-]*)\-(.*)/) || l.match(/(\D*)(.*)/);
  if ( keyValue ) {
    var key   = labelToProperty[keyValue[1]];
    var value = keyValue[2];

    if ( key ) {
      var kv = [key, value.intern()];
      propertyLabels_[l] = kv;
      return kv;
    }
  }

  return propertyLabels_[l] = false;
}

var QIssue = FOAM({
    model_: 'Model',
    extendsModel: 'GeneratedQIssue',

    name: 'QIssue',

    tableProperties: [
      'starred',
      'id',
//      'app',
      'priority',
      'milestone',
      'iteration',
      'releaseBlock',
      'cr',
      'status',
      'owner',
      'summary',
      'OS',
      'modified'
    ],

    ids: ['id'],

    properties: [
        {
          model_: 'IntProperty',
          name: 'id',
          shortName: 'i',
          label: 'ID',
          required: true,
          tableWidth: '48px',
          tableFormatter: function(value, row, table) {
            var id = table.nextID();

            table.on('mouseover', function(e) {
              table.browser.preview(e, value);
            }, id);

            return '
' + value + '
';
          }
        },
        {
          name: 'author',
          preSet: function(_, a) { return a.intern(); },
          aliases: ['reporter']
        },
        {
          model_: 'StringProperty',
          name: 'priority',
          shortName: 'p',
          aliases: ['pr', 'prior'],
          tableLabel: 'Priority',
          tableWidth: '60px',
          compareProperty: function(p1, p2) {
            var priorities = ['Low', 'Medium', 'High', 'Critical'];
            var r = priorities.indexOf(p1) - priorities.indexOf(p2);
            return r === 0 ? 0 : r < 0 ? -1 : 1;
          },
          required: true
        },
        {
          model_: 'IntProperty',
          name: 'pri',
          tableLabel: 'Pri',
          tableWidth: '60px'
        },
        {
          model_: 'StringProperty',
          name: 'app',
          tableWidth: '70px'
        },
        {
          model_: 'StringProperty',
          name: 'type',
          tableWidth: '70px'
        },
        {
            name: 'milestone',
            shortName: 'm',
            aliases: ['mstone'],
            tableLabel: 'M',
            type: 'Int',
            tableWidth: '70px',
            defaultValue: ''
        },
        {
            name: 'iteration',
            shortName: 'it',
            aliases: ['iter'],
            type: 'String',
            tableWidth: '69px'
        },
        {
            name: 'releaseBlock',
            shortName: 'rb',
            aliases: ['rBlock', 'release'],
            type: 'String',
            tableWidth: '103px',
            defaultValue: ''
        },
        {
            name: 'cr',
            shortName: 'c',
            aliases: ['cat', 'cr'],
            label: 'Cr',
            tableWidth: '87px',
            type: 'String',
            defaultValue: ''
        },
        {
            name: 'status',
            shortName: 's',
            aliases: ['stat'],
            type: 'String',
            tableWidth: '58px',
            defaultValue: ''
        },
        {
          name: 'cc',
          preSet: function(_, a) { return a.intern(); },
          autocompleter: 'PersonCompleter'
        },
        {
            name: 'owner',
            shortName: 'o',
            tableWidth: '181px',
            type: 'String',
            autocompleter: 'PersonCompleter',
            preSet: function(_, a) { return a.intern(); }
        },
        {
          name: 'summary',
          shortName: 'su',
          label: 'Summary',
          type: 'String',
          tableWidth: '350px',
//          tableWidth: '100%'
          tableFormatter: function(value, row, tableView) {
            return tableView.strToHTML(value);
          }
        },
        {
          name: 'summaryPlusLabels',
          label: 'Summary + Labels',
          tableLabel: 'Summary + Labels',
          type: 'String',
          tableWidth: '100%',
          tableFormatter: function(value, row, tableView) {
            return tableView.strToHTML(row.summary) +
              QIssue.LABELS.tableFormatter(row.labels, row);
          }
        },
        {
            name: 'OS',
            tableWidth: '61px',
            type: 'String'
        },
        {
          model_: 'BooleanProperty',
          name: 'blocked',
          tableWidth: '20px',
          getter: function() { return !! this.blockedOn.length; }
        },
        {
         model_: 'DateTimeProperty',
         name: 'modified',
         shortName: 'mod',
         mode: 'read-write',
         required: true,
         tableWidth: '100px',
         factory: function() { return new Date(); }
      },
      {
        name: 'updated',
        hidden: true,
        setter: function(v) { this.modified = v; }
      },
      {
         model_: 'BooleanProperty',
         name: 'starred',
         tableLabel: '',
         tableWidth: '18px',
         tableFormatter: function(val, obj, tableView) {
           var view = CSSImageBooleanView.create({
             className: 'star-image',
             data: obj.starred
           });

           view.data$.addListener(function() {
             var tmp = obj.clone();
             tmp.starred = view.data;
             tableView.browser.IssueDAO.put(tmp);
           });

           tableView.addChild(view);
           return view.toHTML();
         },
         view: { model_: 'CSSImageBooleanView', className: 'star-image' },
         help: 'Whether the authenticated user has starred this issue.'
      },
      {
         model_: 'IntProperty',
         name: 'stars',
         tableWidth: '20px',
         help: 'Number of stars this issue has.'
      },
      {
        name: 'labels',
        shortName: 'l',
        aliases: ['label'],
        type: 'String',
        view: 'QIssueLabelsView',
        tableFormatter: function(a, row) {
          var s = '';
          for ( var i = 0 ; i < a.length ; i++ ) {
            if ( ! isPropertyLabel(a[i]) ) {
              s += ' ' + a[i] + '';
            }
          }
          return s;
        },
        postSet: function(_, a) {
          for ( var i = 0 ; i < a.length ; i++ ) {
            var kv = isPropertyLabel(a[i]);
            if ( kv ) {
              this[kv[0]] = kv[1];
              // Don't remove from labels because then label:X searches don't work.
              // a.splice(i,1);
              // i--;
            } else {
              a[i] = a[i].intern();
            }
          }
        }
      },
      {
        model_: 'StringArrayProperty',
        name: 'movedFrom',
        tableWidth: '100px',
        preSet: function(_, v) {
          if ( Array.isArray(v) ) return v;
          if ( ! v ) return undefined;

          return (this.movedFrom || []).binaryInsert(v.charAt(0) == 'M' ? parseInt(v.substring(1)) : parseInt(v));
        },
        compareProperty: function(o1, o2) {
          o1 = o1.length ? o1[0] : 0;
          o2 = o2.length ? o2[0] : 0;
          return o1 === o2 ? 0 : o1 > o2 ? 1 : -1;
        }
      },
      {
        name: 'movedTo',
        preSet: function(_, v) {
          // TODO: looks like we're getting bad values, investigate and fix
          /*
          if ( v.length ) console.log('movedTo: ', v);
          return v;
          */
          return [];
        }
      }
    ],

    methods: {
      isOpen: function() {
        return !! ({
          'New':       true,
          'Accepted':  true,
          'Started':   true,
          'Untriaged': true
        }[this.status]);
      },
      writeActions: function(other, out) {
        var diff = this.diff(other);
        delete diff.starred;

        if ( Object.keys(diff).length == 0 ) return;

        function convertArray(key) {
          if ( ! diff[key] ) {
            diff[key] = [];
            return;
          }

          var delta = diff[key].added;
          for ( var i = 0; i < diff[key].removed.length; i++ )
            delta.push("-" + diff[key].removed[i]);
          diff[key] = delta;
        }

        convertArray('blockedOn');
        convertArray('blocking');
        convertArray('cc');
        convertArray('labels');

        function propToLabel(prop, label) {
          if ( diff[prop] ) {
            diff.labels = diff.labels.concat(
              '-' + label + '-' + other[prop],
              label + '-' + diff[prop]);
            delete diff[prop];
          }
        }

        propToLabel('priority', 'Priority');
        propToLabel('app', 'App');
        propToLabel('milestone', 'Milestone');
        propToLabel('cr', 'Cr');
        propToLabel('iteration', 'Iteration');
        propToLabel('releaseBlock', 'ReleaseBlock');
        propToLabel('OS', 'OS');

        var comment = QIssueComment.create({
          issueId: this.id,
          updates: QIssueCommentUpdate.create(diff)
        });

        out(comment);
      },
      newComment: function() {
        return QIssueComment.create({
          issueId: this.id,
          updates: QIssueCommentUpdate.create({
            labels: this.labels.clone(),
            owner: this.owner,
            blockedOn: this.blockedOn.clone(),
            blocking: this.blocking.clone(),
            cc: this.cc.clone(),
            mergedInto: this.mergedInto,
            status: this.status,
            summary: this.summary
          })
        })
      }
    }
});

QIssue.getPrototype();

QIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
});

GeneratedQIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
  if ( ! p["tableWidth"] ) {
    p["tableWidth"] = '80px;'
  }
});

QIssue.LABELS.toMQL = function() { return 'label'; };
<|endoftext|>


<commit_before>// @flow
import { withData, withRecall, withReset } from 'spunky'
import { compose } from 'recompose'

import settingsActions from '../actions/settingsActions'
import icoTokensActions from '../actions/icoTokensActions'
import balancesActions from '../actions/balancesActions'

export default function withTokensData(key: string = 'tokens') {
  return compose(
    withData(settingsActions, settings => ({ [key]: settings.tokens })),
    withRecall(icoTokensActions, ['tokens']),
    withReset(balancesActions, ['tokens'])
  )
}
<commit_msg>reverts changes to app/hocs/withTokensData.js<commit_after>// @flow
import { withData, withRecall } from 'spunky'
import { compose } from 'recompose'

import settingsActions from '../actions/settingsActions'
import icoTokensActions from '../actions/icoTokensActions'

export default function withTokensData(key: string = 'tokens') {
  return compose(
    withData(settingsActions, settings => ({ [key]: settings.tokens })),
    withRecall(icoTokensActions, ['tokens'])
  )
}
<|endoftext|>


<commit_before>var schedule = [,[[490,540,"1st Period"],[545,600,"2nd Period"],[600,610,"Brunch"],[615,665,"3rd Period"],[670,720,"4th Period"],[720,765,"Lunch"],[770,820,"5th Period"],[825,875,"6th Period"],[880,930,"7th Period"]],[[490,535,"1st Period"],[540,590,"2nd Period"],[590,600,"Brunch"],[605,650,"3rd Period"],[655,685,"Tutorial"],[690,735,"4th Period"],[735,780,"Lunch"],[785,830,"5th Period"],[835,880,"6th Period"],[885,930,"7th Period"]],[[530,625,"2nd Period"],[625,640,"Brunch"],[645,735,"4th Period"],[735,780,"Lunch"],[785,875,"6th Period"]],[[490,585,"1st Period"],[585,600,"Brunch"],[605,695,"3rd Period"],[695,740,"Lunch"],[745,835,"5th Period"],[840,930,"7th Period"]],[[490,540,"1st Period"],[545,600,"2nd Period"],[600,610,"Brunch"],[615,665,"3rd Period"],[670,720,"4th Period"],[720,765,"Lunch"],[770,820,"5th Period"],[825,875,"6th Period"],[880,930,"7th Period"]]];

var currentPeriod;

if(document.readyState == "complete"){
    tick();
}
else {
    document.addEventListener("DOMContentLoaded", tick);
}

function tick(){
    var periodElem = document.getElementById("period"),
        textElem = document.getElementsByTagName("text")[0],
        time = getMinuteTime(),
        day = schedule[new Date().getDay()];

    if(day && time >= day[0][0] && time < day[day.length-1][1]){
        var period = getPeriod(time, day);

        periodElem.innerHTML = period[2];
        document.title = textElem.innerHTML = createTimeLeftString(period[1]);

        if(!currentPeriod){
            requestAnimationFrame(updateProgressBar);
        }
        currentPeriod = period;
    }
    else {
        currentPeriod = null;
        periodElem.innerHTML = document.title = "No School";
        textElem.innerHTML = "";

        var path = document.getElementById("red"),
            length = path.getTotalLength();

        path.style.strokeDasharray = length + " " + length;
        path.style.strokeDashoffset = length;
    }
    setTimeout(tick, 1000 - (Date.now() % 1000));
};

function createTimeLeftString(time){
    var secondsLeft = -secSinceMinTime(time),
        h = Math.floor(secondsLeft / 3600),
        m = Math.floor((secondsLeft % 3600) / 60),
        s = secondsLeft % 60;

    m = (m < 10 ? "0" : "") + m;
    s = (s < 10 ? "0" : "") + s;

    if(h > 0){
        return h + ":" + m + ":" + s;
    }
    else if(m > 0){
        return m + ":" + s;
    }
    return "00:" + s;
}

function getPeriod(time, day){
    for(var i = 0; i < day.length; i++){
        if(time >= day[i][0] && time < day[i][1]){
            return day[i];
        }
        else if(i < day.length - 1 && time >= day[i][1] && time < day[i+1][0]){
            return [day[i][1], day[i+1][0], "Passing Period"];
        }
    }
}

function updateProgressBar(){
    if(!currentPeriod){
        return;
    }
    var path = document.getElementById("red"),
        length = path.getTotalLength();

    path.style.strokeDasharray = length + " " + length;
    path.style.strokeDashoffset = length * (1 - percentPeriodElapsed(currentPeriod));
    requestAnimationFrame(updateProgressBar);
}

function getMinuteTime(){
    var date = new Date();
    return date.getHours() * 60 + date.getMinutes();
}

function percentPeriodElapsed(period){
    var secondsInPeriod = 60 * (period[1] - period[0]);
    return secSinceMinTime(period[0]) / secondsInPeriod;
}

function secSinceMinTime(time){
    return 60 * (getMinuteTime() - time) + new Date().getSeconds();
}
<commit_msg>Remove unecessary semicolon<commit_after>var schedule = [,[[490,540,"1st Period"],[545,600,"2nd Period"],[600,610,"Brunch"],[615,665,"3rd Period"],[670,720,"4th Period"],[720,765,"Lunch"],[770,820,"5th Period"],[825,875,"6th Period"],[880,930,"7th Period"]],[[490,535,"1st Period"],[540,590,"2nd Period"],[590,600,"Brunch"],[605,650,"3rd Period"],[655,685,"Tutorial"],[690,735,"4th Period"],[735,780,"Lunch"],[785,830,"5th Period"],[835,880,"6th Period"],[885,930,"7th Period"]],[[530,625,"2nd Period"],[625,640,"Brunch"],[645,735,"4th Period"],[735,780,"Lunch"],[785,875,"6th Period"]],[[490,585,"1st Period"],[585,600,"Brunch"],[605,695,"3rd Period"],[695,740,"Lunch"],[745,835,"5th Period"],[840,930,"7th Period"]],[[490,540,"1st Period"],[545,600,"2nd Period"],[600,610,"Brunch"],[615,665,"3rd Period"],[670,720,"4th Period"],[720,765,"Lunch"],[770,820,"5th Period"],[825,875,"6th Period"],[880,930,"7th Period"]]];

var currentPeriod;

if(document.readyState == "complete"){
    tick();
}
else {
    document.addEventListener("DOMContentLoaded", tick);
}

function tick(){
    var periodElem = document.getElementById("period"),
        textElem = document.getElementsByTagName("text")[0],
        time = getMinuteTime(),
        day = schedule[new Date().getDay()];

    if(day && time >= day[0][0] && time < day[day.length-1][1]){
        var period = getPeriod(time, day);

        periodElem.innerHTML = period[2];
        document.title = textElem.innerHTML = createTimeLeftString(period[1]);

        if(!currentPeriod){
            requestAnimationFrame(updateProgressBar);
        }
        currentPeriod = period;
    }
    else {
        currentPeriod = null;
        periodElem.innerHTML = document.title = "No School";
        textElem.innerHTML = "";

        var path = document.getElementById("red"),
            length = path.getTotalLength();

        path.style.strokeDasharray = length + " " + length;
        path.style.strokeDashoffset = length;
    }
    setTimeout(tick, 1000 - (Date.now() % 1000));
}

function createTimeLeftString(time){
    var secondsLeft = -secSinceMinTime(time),
        h = Math.floor(secondsLeft / 3600),
        m = Math.floor((secondsLeft % 3600) / 60),
        s = secondsLeft % 60;

    m = (m < 10 ? "0" : "") + m;
    s = (s < 10 ? "0" : "") + s;

    if(h > 0){
        return h + ":" + m + ":" + s;
    }
    else if(m > 0){
        return m + ":" + s;
    }
    return "00:" + s;
}

function getPeriod(time, day){
    for(var i = 0; i < day.length; i++){
        if(time >= day[i][0] && time < day[i][1]){
            return day[i];
        }
        else if(i < day.length - 1 && time >= day[i][1] && time < day[i+1][0]){
            return [day[i][1], day[i+1][0], "Passing Period"];
        }
    }
}

function updateProgressBar(){
    if(!currentPeriod){
        return;
    }
    var path = document.getElementById("red"),
        length = path.getTotalLength();

    path.style.strokeDasharray = length + " " + length;
    path.style.strokeDashoffset = length * (1 - percentPeriodElapsed(currentPeriod));
    requestAnimationFrame(updateProgressBar);
}

function getMinuteTime(){
    var date = new Date();
    return date.getHours() * 60 + date.getMinutes();
}

function percentPeriodElapsed(period){
    var secondsInPeriod = 60 * (period[1] - period[0]);
    return secSinceMinTime(period[0]) / secondsInPeriod;
}

function secSinceMinTime(time){
    return 60 * (getMinuteTime() - time) + new Date().getSeconds();
}
<|endoftext|>


<commit_before>var boot = require('../');
var fs = require('fs-extra');
var path = require('path');
var expect = require('must');
var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir');

var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');

describe('compiler', function() {
  beforeEach(sandbox.reset);
  beforeEach(appdir.init);

  describe('from options', function() {
    var options, instructions, appConfig;
    beforeEach(function() {
      options = {
        config: {
          port: 3000,
          host: '127.0.0.1',
          restApiRoot: '/rest-api',
          foo: {bar: 'bat'},
          baz: true
        },
        models: {
          'foo-bar-bat-baz': {
            dataSource: 'the-db'
          }
        },
        dataSources: {
          'the-db': {
            connector: 'memory',
            defaultForType: 'db'
          }
        }
      };
      instructions = boot.compile(options);
      appConfig = instructions.config;
    });

    it('has port setting', function() {
      expect(appConfig).to.have.property('port', 3000);
    });

    it('has host setting', function() {
      expect(appConfig).to.have.property('host', '127.0.0.1');
    });

    it('has restApiRoot setting', function() {
      expect(appConfig).to.have.property('restApiRoot', '/rest-api');
    });

    it('has other settings', function() {
      expect(appConfig).to.have.property('baz', true);
      expect(appConfig.foo, 'appConfig.foo').to.eql({
        bar: 'bat'
      });
    });

    it('has models definition', function() {
      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'foo-bar-bat-baz',
        config: {
          dataSource: 'the-db'
        },
        definition: undefined,
        sourceFile: undefined
      });
    });

    it('has datasources definition', function() {
      expect(instructions.dataSources).to.eql(options.dataSources);
    });
  });

  describe('from directory', function() {
    it('loads config files', function() {
      var instructions = boot.compile(SIMPLE_APP);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'User',
        config: {
          dataSource: 'db'
        },
        definition: undefined,
        sourceFile: undefined
      });
    });

    it('merges datasource configs from multiple files', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { local: 'applied' }
      });

      var env = process.env.NODE_ENV || 'development';
      appdir.writeConfigFileSync('datasources.' + env + '.json', {
        db: { env: 'applied' }
      });

      var instructions = boot.compile(appdir.PATH);

      var db = instructions.dataSources.db;
      expect(db).to.have.property('local', 'applied');
      expect(db).to.have.property('env', 'applied');

      var expectedLoadOrder = ['local', 'env'];
      var actualLoadOrder = Object.keys(db).filter(function(k) {
        return expectedLoadOrder.indexOf(k) !== -1;
      });

      expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
    });

    it('supports .js for custom datasource config files', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('datasources.local.js',
        'module.exports = { db: { fromJs: true } };');

      var instructions = boot.compile(appdir.PATH);

      var db = instructions.dataSources.db;
      expect(db).to.have.property('fromJs', true);
    });

    it('refuses to merge Object properties', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { nested: { key: 'value' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/`nested` is not a value type/);
    });

    it('refuses to merge Array properties', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { nested: ['value'] }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/`nested` is not a value type/);
    });

    it('merges app configs from multiple files', function() {
      appdir.createConfigFilesSync();

      appdir.writeConfigFileSync('config.local.json', { cfgLocal: 'applied' });

      var env = process.env.NODE_ENV || 'development';
      appdir.writeConfigFileSync('config.' + env + '.json', { cfgEnv: 'applied' });

      var instructions = boot.compile(appdir.PATH);
      var appConfig = instructions.config;

      expect(appConfig).to.have.property('cfgLocal', 'applied');
      expect(appConfig).to.have.property('cfgEnv', 'applied');

      var expectedLoadOrder = ['cfgLocal', 'cfgEnv'];
      var actualLoadOrder = Object.keys(appConfig).filter(function(k) {
        return expectedLoadOrder.indexOf(k) !== -1;
      });

      expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
    });

    it('supports .js for custom app config files', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('config.local.js',
        'module.exports = { fromJs: true };');

      var instructions = boot.compile(appdir.PATH);
      var appConfig = instructions.config;

      expect(appConfig).to.have.property('fromJs', true);
    });

    it('supports `dsRootDir` option', function() {
      appdir.createConfigFilesSync();

      var customDir = path.resolve(appdir.PATH, 'custom');
      fs.mkdirsSync(customDir);
      fs.renameSync(
        path.resolve(appdir.PATH, 'datasources.json'),
        path.resolve(customDir, 'datasources.json'));

      var instructions = boot.compile({
        appRootDir: appdir.PATH,
        dsRootDir: path.resolve(appdir.PATH, 'custom')
      });

      expect(instructions.dataSources).to.have.property('db');
    });

    it('supports `modelsRootDir` option', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('custom/models.json', {
        foo: { dataSource: 'db' }
      });

      var instructions = boot.compile({
        appRootDir: appdir.PATH,
        modelsRootDir: path.resolve(appdir.PATH, 'custom')
      });

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.have.property('name', 'foo');
    });

    it('includes boot/*.js scripts', function() {
      appdir.createConfigFilesSync();
      var initJs = appdir.writeFileSync('boot/init.js',
        'module.exports = function(app) { app.fnCalled = true; };');
      var instructions = boot.compile(appdir.PATH);
      expect(instructions.files.boot).to.eql([initJs]);
    });

    it('ignores models/ subdirectory', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('models/my-model.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.files).to.not.have.property('models');
    });

    it('throws when models.json contains `properties` from 1.x', function() {
      appdir.createConfigFilesSync({}, {}, {
        foo: { properties: { name: 'string' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/unsupported 1\.x format/);
    });

    it('throws when models.json contains `options.base` from 1.x', function() {
      appdir.createConfigFilesSync({}, {}, {
        Customer: { options: { base: 'User' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/unsupported 1\.x format/);
    });

    it('loads models from `./models`', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });
      appdir.writeFileSync('models/car.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: path.resolve(appdir.PATH, 'models', 'car.js')
      });
    });

    it('supports `sources` option in `models.json`', function() {
      appdir.createConfigFilesSync({}, {}, {
        _meta: {
          sources: ['./custom-models']
        },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' });
      appdir.writeFileSync('custom-models/car.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: path.resolve(appdir.PATH, 'custom-models', 'car.js')
      });
    });

    it('handles model definitions with no code', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.eql([{
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: undefined
      }]);
    });

    it('excludes models not listed in `models.json`', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });
      appdir.writeConfigFileSync('models/bar.json', { name: 'Bar' });

      var instructions = boot.compile(appdir.PATH);

      var models = instructions.models.map(getNameProperty);
      expect(models).to.eql(['Car']);
    });

    it('includes models used as Base models', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle'
      });

      var instructions = boot.compile(appdir.PATH);
      var models = instructions.models;
      var modelNames = models.map(getNameProperty);

      expect(modelNames).to.eql(['Vehicle', 'Car']);
      expect(models[0].config).to.equal(undefined);
    });

    it('excludes pre-built base models', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Model'
      });

      var instructions = boot.compile(appdir.PATH);

      var modelNames = instructions.models.map(getNameProperty);
      expect(modelNames).to.eql(['Car']);
    });

    it('sorts models, base models first', function() {
      appdir.createConfigFilesSync({}, {}, {
        Vehicle: { dataSource: 'db' },
        FlyingCar: { dataSource: 'db' },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/flying-car.json', {
        name: 'FlyingCar',
        base: 'Car'
      });

      var instructions = boot.compile(appdir.PATH);

      var modelNames = instructions.models.map(getNameProperty);
      expect(modelNames).to.eql(['Vehicle', 'Car', 'FlyingCar']);
    });

    it('detects circular Model dependencies', function() {
      appdir.createConfigFilesSync({}, {}, {
        Vehicle: { dataSource: 'db' },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle',
        base: 'Car'
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/cyclic dependency/i);
    });
  });
});

function getNameProperty(obj) {
  return obj.name;
}
<commit_msg>test: fix jshint warnings<commit_after>var boot = require('../');
var fs = require('fs-extra');
var path = require('path');
var expect = require('must');
var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir');

var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');

describe('compiler', function() {
  beforeEach(sandbox.reset);
  beforeEach(appdir.init);

  describe('from options', function() {
    var options, instructions, appConfig;
    beforeEach(function() {
      options = {
        config: {
          port: 3000,
          host: '127.0.0.1',
          restApiRoot: '/rest-api',
          foo: {bar: 'bat'},
          baz: true
        },
        models: {
          'foo-bar-bat-baz': {
            dataSource: 'the-db'
          }
        },
        dataSources: {
          'the-db': {
            connector: 'memory',
            defaultForType: 'db'
          }
        }
      };
      instructions = boot.compile(options);
      appConfig = instructions.config;
    });

    it('has port setting', function() {
      expect(appConfig).to.have.property('port', 3000);
    });

    it('has host setting', function() {
      expect(appConfig).to.have.property('host', '127.0.0.1');
    });

    it('has restApiRoot setting', function() {
      expect(appConfig).to.have.property('restApiRoot', '/rest-api');
    });

    it('has other settings', function() {
      expect(appConfig).to.have.property('baz', true);
      expect(appConfig.foo, 'appConfig.foo').to.eql({
        bar: 'bat'
      });
    });

    it('has models definition', function() {
      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'foo-bar-bat-baz',
        config: {
          dataSource: 'the-db'
        },
        definition: undefined,
        sourceFile: undefined
      });
    });

    it('has datasources definition', function() {
      expect(instructions.dataSources).to.eql(options.dataSources);
    });
  });

  describe('from directory', function() {
    it('loads config files', function() {
      var instructions = boot.compile(SIMPLE_APP);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'User',
        config: {
          dataSource: 'db'
        },
        definition: undefined,
        sourceFile: undefined
      });
    });

    it('merges datasource configs from multiple files', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { local: 'applied' }
      });

      var env = process.env.NODE_ENV || 'development';
      appdir.writeConfigFileSync('datasources.' + env + '.json', {
        db: { env: 'applied' }
      });

      var instructions = boot.compile(appdir.PATH);

      var db = instructions.dataSources.db;
      expect(db).to.have.property('local', 'applied');
      expect(db).to.have.property('env', 'applied');

      var expectedLoadOrder = ['local', 'env'];
      var actualLoadOrder = Object.keys(db).filter(function(k) {
        return expectedLoadOrder.indexOf(k) !== -1;
      });

      expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
    });

    it('supports .js for custom datasource config files', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('datasources.local.js',
        'module.exports = { db: { fromJs: true } };');

      var instructions = boot.compile(appdir.PATH);

      var db = instructions.dataSources.db;
      expect(db).to.have.property('fromJs', true);
    });

    it('refuses to merge Object properties', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { nested: { key: 'value' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/`nested` is not a value type/);
    });

    it('refuses to merge Array properties', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('datasources.local.json', {
        db: { nested: ['value'] }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/`nested` is not a value type/);
    });

    it('merges app configs from multiple files', function() {
      appdir.createConfigFilesSync();

      appdir.writeConfigFileSync('config.local.json', { cfgLocal: 'applied' });

      var env = process.env.NODE_ENV || 'development';
      appdir.writeConfigFileSync('config.' + env + '.json',
        { cfgEnv: 'applied' });

      var instructions = boot.compile(appdir.PATH);
      var appConfig = instructions.config;

      expect(appConfig).to.have.property('cfgLocal', 'applied');
      expect(appConfig).to.have.property('cfgEnv', 'applied');

      var expectedLoadOrder = ['cfgLocal', 'cfgEnv'];
      var actualLoadOrder = Object.keys(appConfig).filter(function(k) {
        return expectedLoadOrder.indexOf(k) !== -1;
      });

      expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
    });

    it('supports .js for custom app config files', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('config.local.js',
        'module.exports = { fromJs: true };');

      var instructions = boot.compile(appdir.PATH);
      var appConfig = instructions.config;

      expect(appConfig).to.have.property('fromJs', true);
    });

    it('supports `dsRootDir` option', function() {
      appdir.createConfigFilesSync();

      var customDir = path.resolve(appdir.PATH, 'custom');
      fs.mkdirsSync(customDir);
      fs.renameSync(
        path.resolve(appdir.PATH, 'datasources.json'),
        path.resolve(customDir, 'datasources.json'));

      var instructions = boot.compile({
        appRootDir: appdir.PATH,
        dsRootDir: path.resolve(appdir.PATH, 'custom')
      });

      expect(instructions.dataSources).to.have.property('db');
    });

    it('supports `modelsRootDir` option', function() {
      appdir.createConfigFilesSync();
      appdir.writeConfigFileSync('custom/models.json', {
        foo: { dataSource: 'db' }
      });

      var instructions = boot.compile({
        appRootDir: appdir.PATH,
        modelsRootDir: path.resolve(appdir.PATH, 'custom')
      });

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.have.property('name', 'foo');
    });

    it('includes boot/*.js scripts', function() {
      appdir.createConfigFilesSync();
      var initJs = appdir.writeFileSync('boot/init.js',
        'module.exports = function(app) { app.fnCalled = true; };');
      var instructions = boot.compile(appdir.PATH);
      expect(instructions.files.boot).to.eql([initJs]);
    });

    it('ignores models/ subdirectory', function() {
      appdir.createConfigFilesSync();
      appdir.writeFileSync('models/my-model.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.files).to.not.have.property('models');
    });

    it('throws when models.json contains `properties` from 1.x', function() {
      appdir.createConfigFilesSync({}, {}, {
        foo: { properties: { name: 'string' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/unsupported 1\.x format/);
    });

    it('throws when models.json contains `options.base` from 1.x', function() {
      appdir.createConfigFilesSync({}, {}, {
        Customer: { options: { base: 'User' } }
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/unsupported 1\.x format/);
    });

    it('loads models from `./models`', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });
      appdir.writeFileSync('models/car.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: path.resolve(appdir.PATH, 'models', 'car.js')
      });
    });

    it('supports `sources` option in `models.json`', function() {
      appdir.createConfigFilesSync({}, {}, {
        _meta: {
          sources: ['./custom-models']
        },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' });
      appdir.writeFileSync('custom-models/car.js', '');

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.have.length(1);
      expect(instructions.models[0]).to.eql({
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: path.resolve(appdir.PATH, 'custom-models', 'car.js')
      });
    });

    it('handles model definitions with no code', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });

      var instructions = boot.compile(appdir.PATH);

      expect(instructions.models).to.eql([{
        name: 'Car',
        config: {
          dataSource: 'db'
        },
        definition: {
          name: 'Car'
        },
        sourceFile: undefined
      }]);
    });

    it('excludes models not listed in `models.json`', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', { name: 'Car' });
      appdir.writeConfigFileSync('models/bar.json', { name: 'Bar' });

      var instructions = boot.compile(appdir.PATH);

      var models = instructions.models.map(getNameProperty);
      expect(models).to.eql(['Car']);
    });

    it('includes models used as Base models', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle'
      });

      var instructions = boot.compile(appdir.PATH);
      var models = instructions.models;
      var modelNames = models.map(getNameProperty);

      expect(modelNames).to.eql(['Vehicle', 'Car']);
      expect(models[0].config).to.equal(undefined);
    });

    it('excludes pre-built base models', function() {
      appdir.createConfigFilesSync({}, {}, {
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Model'
      });

      var instructions = boot.compile(appdir.PATH);

      var modelNames = instructions.models.map(getNameProperty);
      expect(modelNames).to.eql(['Car']);
    });

    it('sorts models, base models first', function() {
      appdir.createConfigFilesSync({}, {}, {
        Vehicle: { dataSource: 'db' },
        FlyingCar: { dataSource: 'db' },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/flying-car.json', {
        name: 'FlyingCar',
        base: 'Car'
      });

      var instructions = boot.compile(appdir.PATH);

      var modelNames = instructions.models.map(getNameProperty);
      expect(modelNames).to.eql(['Vehicle', 'Car', 'FlyingCar']);
    });

    it('detects circular Model dependencies', function() {
      appdir.createConfigFilesSync({}, {}, {
        Vehicle: { dataSource: 'db' },
        Car: { dataSource: 'db' }
      });
      appdir.writeConfigFileSync('models/car.json', {
        name: 'Car',
        base: 'Vehicle'
      });
      appdir.writeConfigFileSync('models/vehicle.json', {
        name: 'Vehicle',
        base: 'Car'
      });

      expect(function() { boot.compile(appdir.PATH); })
        .to.throw(/cyclic dependency/i);
    });
  });
});

function getNameProperty(obj) {
  return obj.name;
}
<|endoftext|>


<commit_before>var iframeCounter = 0

Element.addMethods('form', {
  hasFileUploads: function(form) {
    return $(form).select('input[type=file]').any(function(input) {
      return input.getValue()
    })
  },
  isDirty: function(form) {
    form = $(form)
    return form.hasFileUploads() ||
      form.select('textarea').any(function(area) { return area.getValue() != area.innerHTML }) ||
      form.select('input:not([type=submit],[type=hidden],[type=checkbox],[type=radio])').
        any(function(input) { return input.getValue() != (input.readAttribute('value') || '') }) ||
      form.select('select').any(function(select) {
        option = select.down('option[selected]') || select.down('option')
        select.getValue() != option.value
      })
  }
})

document.on('ajax:create', 'form.new_conversation, .thread form', function(e) {
  // don't favor RJS; state we want HTML instead
  e.memo.request.options.requestHeaders = {'Accept': 'text/html'}
})

// async file uploads in comments via iframe
document.on('ajax:before', 'form.new_conversation, .thread form, #facebox form.edit_comment', function(e, form) {
  if (form.hasFileUploads()) {
    e.stop()

    var iframeID = 'file_upload_iframe' + (iframeCounter++)
    var iframe = new Element('iframe', { id: iframeID, name: iframeID }).hide()
    $(document.body).insert(iframe)
    form.target = iframeID
    form.insert(new Element('input', { type: 'hidden', name: 'iframe', value: 'true' }))

    var callback = function() {
      // contentDocument doesn't work in IE (7)
      var iframeBody = (iframe.contentDocument || iframe.contentWindow.document).body

      if (iframeBody.className == "error") {
        var json = iframeBody.firstChild.innerHTML.evalJSON()
        form.fire('ajax:failure', {responseJSON: json})
      } else {
        form.fire('ajax:success', {responseText: iframeBody.innerHTML})
      }

      form.fire('ajax:complete')
      iframe.remove()
      form.target = null
      var extraInput = form.down('input[name=iframe]')
      if (extraInput) extraInput.remove()
    }

    // for IE (7)
    iframe.onreadystatechange = function() {
      if (this.readyState == 'complete') callback()
    }
    // non-IE
    iframe.onload = callback

    // we may have cancelled xhr, but we still need to trigger form submit manually
    form.submit()
  }
})

function resetCommentsForm(form) {
  // clear comment and reset textarea height
  form.down('textarea[name*="[body]"]').setValue('').setStyle({ height: '' })
  // clear populated file uploads
  form.select('input[type=file]').each(function(input) {
    if (input.getValue()) input.remove()
  })
  // clear hours
  var hours = form.down('input[name*="[human_hours]"]')
  if (hours) hours.setValue('')
  // hide initially hidden areas
  form.select('.hours_field, .upload_area').invoke('hide')
  // clear errors
  form.select('.error').invoke('remove')
  //clear google docs hidden fields and list items in the file list
  form.select('.google_docs_attachment .fields input').invoke('remove')
  form.select('.google_docs_attachment .file_list li').invoke('remove')
}

// insert new simple conversation into stream after posting
document.on('ajax:success', 'form.new_conversation', function(e, form) {
  resetCommentsForm(form);
  $('activities').insert({top: e.memo.responseText}).down('.thread').highlight({ duration: 1 });
  Task.insertAssignableUsers();

  //disable _method input field for conversation forms on inserting simple conversations
  disableConversationHttpMethodField();
})

// "Show N previous comments" action in threads
document.on('ajax:success', '.thread .comments .more_comments', function(e, el) {
  el.up('.comments').update(e.memo.responseText).highlight({ duration: 2 })
})

document.on('click', '.thread .comments .more_comments a', function(e, el) {
  el.update("")
})

// insert new comment into thread after posting
document.on('ajax:success', '.thread form:not(.not-new-comment)', function(e, form) {
  resetCommentsForm(form)
  if (!e.memo.responseText.blank()) {
    form.up('.thread').down('.comments').insert(e.memo.responseText).
      down('.comment:last-child').highlight({ duration: 1 })
  }
})

document.on('ajax:failure', 'form.new_conversation, .thread form:not(.not-new-comment)', function(e, form) {
  var message = $H(e.memo.responseJSON)
	message.each( function(error) {
		form.down('div.text_area').insertOrUpdate('p.error', error.value)
	})
})

// update edited comment
document.on('ajax:success', '#facebox form.edit_comment', function(e, form) {
  var commentID = form.readAttribute('action').match(/\d+/g).last()
  $('comment_' + commentID).replace(e.memo.responseText)
  Prototype.Facebox.close()
  $('comment_' + commentID).highlight()
})

// remove deleted comment
document.on('ajax:success', '.comment:not(div[data-class=conversation].thread .comment) .actions_menu a[data-method=delete]', function(e, link) {
  e.findElement('.comment').remove()
})

// when deleting comment, remove the conversation if empty: no comments, no title.
document.on('ajax:success', 'div[data-class=conversation].thread .comment .actions_menu a[data-method=delete]', function(e, link) {
	var conversation = e.findElement('.thread')
	if (conversation.select('.comment').length == 1 && conversation.select('.title').length == 0) conversation.remove()
	else e.findElement('.comment').remove()
})

// toggle between hidden upload area and a link to show it
hideBySelector('form .upload_area')

document.on('click', 'form .attach_icon', function(e, link) {
  if (!e.isMiddleClick()) {
    link.up('form').down('.upload_area').forceShow().highlight()
    e.stop()
  }
})

// toggle between hidden time tracking input and a link to show it
hideBySelector('form .hours_field')

document.on('click', 'form .add_hours_icon', function(e, link) {
  link.up('form').down('.hours_field').forceShow().down('input').focus()
  e.stop()
})

// Open links inside Comments and Notes textilized areas in new windows
document.on('mouseover', '.textilized a', function(e, link) {
  link.writeAttribute("target", "_blank");
});

hideBySelector('#activities .thread form.new_comment .extra')

document.on('focusin', '#activities .thread form.new_comment textarea', function(e, input) {
  input.up('form').down('.extra').forceShow()
})

// document.on('focusout', '.thread form.new_comment textarea', function(e, input) {
//   if (input.getValue().empty()) {
//     input.up('form').down('.extra').hide()
//   }
// })

// enable username autocompletion for main textarea in comment forms
document.on('focusin', 'form textarea[name*="[body]"]', function(e, input) {
  var form = e.findElement('form'),
      project = form.readAttribute('data-project-id')

  // projects index page has a global comment box with projects selector
  if (!project) {
    var projectSelect = form.down('select[name=project_id]')
    if (projectSelect) project = projectSelect.getValue()
  }

  if (project) {
    var people = _people_autocomplete[project],
        autocompleter = input.retrieve('autocompleter')

    if (autocompleter) {
      // update options array in case the projects selector changed value
      autocompleter.options.array = people
    } else {
      var container = new Element('div', { 'class': 'autocomplete' }).hide()
      input.insert({ after: container })
      autocompleter = new Autocompleter.Local(input, container, people, { tokens:[' '] })
      input.store('autocompleter', autocompleter)
    }
  }
})
<commit_msg>Use blindDown to show more comments<commit_after>var iframeCounter = 0

Element.addMethods('form', {
  hasFileUploads: function(form) {
    return $(form).select('input[type=file]').any(function(input) {
      return input.getValue()
    })
  },
  isDirty: function(form) {
    form = $(form)
    return form.hasFileUploads() ||
      form.select('textarea').any(function(area) { return area.getValue() != area.innerHTML }) ||
      form.select('input:not([type=submit],[type=hidden],[type=checkbox],[type=radio])').
        any(function(input) { return input.getValue() != (input.readAttribute('value') || '') }) ||
      form.select('select').any(function(select) {
        option = select.down('option[selected]') || select.down('option')
        select.getValue() != option.value
      })
  }
})

document.on('ajax:create', 'form.new_conversation, .thread form', function(e) {
  // don't favor RJS; state we want HTML instead
  e.memo.request.options.requestHeaders = {'Accept': 'text/html'}
})

// async file uploads in comments via iframe
document.on('ajax:before', 'form.new_conversation, .thread form, #facebox form.edit_comment', function(e, form) {
  if (form.hasFileUploads()) {
    e.stop()

    var iframeID = 'file_upload_iframe' + (iframeCounter++)
    var iframe = new Element('iframe', { id: iframeID, name: iframeID }).hide()
    $(document.body).insert(iframe)
    form.target = iframeID
    form.insert(new Element('input', { type: 'hidden', name: 'iframe', value: 'true' }))

    var callback = function() {
      // contentDocument doesn't work in IE (7)
      var iframeBody = (iframe.contentDocument || iframe.contentWindow.document).body

      if (iframeBody.className == "error") {
        var json = iframeBody.firstChild.innerHTML.evalJSON()
        form.fire('ajax:failure', {responseJSON: json})
      } else {
        form.fire('ajax:success', {responseText: iframeBody.innerHTML})
      }

      form.fire('ajax:complete')
      iframe.remove()
      form.target = null
      var extraInput = form.down('input[name=iframe]')
      if (extraInput) extraInput.remove()
    }

    // for IE (7)
    iframe.onreadystatechange = function() {
      if (this.readyState == 'complete') callback()
    }
    // non-IE
    iframe.onload = callback

    // we may have cancelled xhr, but we still need to trigger form submit manually
    form.submit()
  }
})

function resetCommentsForm(form) {
  // clear comment and reset textarea height
  form.down('textarea[name*="[body]"]').setValue('').setStyle({ height: '' })
  // clear populated file uploads
  form.select('input[type=file]').each(function(input) {
    if (input.getValue()) input.remove()
  })
  // clear hours
  var hours = form.down('input[name*="[human_hours]"]')
  if (hours) hours.setValue('')
  // hide initially hidden areas
  form.select('.hours_field, .upload_area').invoke('hide')
  // clear errors
  form.select('.error').invoke('remove')
  //clear google docs hidden fields and list items in the file list
  form.select('.google_docs_attachment .fields input').invoke('remove')
  form.select('.google_docs_attachment .file_list li').invoke('remove')
}

// insert new simple conversation into stream after posting
document.on('ajax:success', 'form.new_conversation', function(e, form) {
  resetCommentsForm(form);
  $('activities').insert({top: e.memo.responseText}).down('.thread').highlight({ duration: 1 });
  Task.insertAssignableUsers();

  //disable _method input field for conversation forms on inserting simple conversations
  disableConversationHttpMethodField();
})

// "Show N previous comments" action in threads
document.on('ajax:success', '.thread .comments .more_comments', function(e, el) {
  el.up('.comments').update(e.memo.responseText).blindDown({ duration: 0.5 })
})

document.on('click', '.thread .comments .more_comments a', function(e, el) {
  el.update("")
})

// insert new comment into thread after posting
document.on('ajax:success', '.thread form:not(.not-new-comment)', function(e, form) {
  resetCommentsForm(form)
  if (!e.memo.responseText.blank()) {
    form.up('.thread').down('.comments').insert(e.memo.responseText).
      down('.comment:last-child').highlight({ duration: 1 })
  }
})

document.on('ajax:failure', 'form.new_conversation, .thread form:not(.not-new-comment)', function(e, form) {
  var message = $H(e.memo.responseJSON)
	message.each( function(error) {
		form.down('div.text_area').insertOrUpdate('p.error', error.value)
	})
})

// update edited comment
document.on('ajax:success', '#facebox form.edit_comment', function(e, form) {
  var commentID = form.readAttribute('action').match(/\d+/g).last()
  $('comment_' + commentID).replace(e.memo.responseText)
  Prototype.Facebox.close()
  $('comment_' + commentID).highlight()
})

// remove deleted comment
document.on('ajax:success', '.comment:not(div[data-class=conversation].thread .comment) .actions_menu a[data-method=delete]', function(e, link) {
  e.findElement('.comment').remove()
})

// when deleting comment, remove the conversation if empty: no comments, no title.
document.on('ajax:success', 'div[data-class=conversation].thread .comment .actions_menu a[data-method=delete]', function(e, link) {
	var conversation = e.findElement('.thread')
	if (conversation.select('.comment').length == 1 && conversation.select('.title').length == 0) conversation.remove()
	else e.findElement('.comment').remove()
})

// toggle between hidden upload area and a link to show it
hideBySelector('form .upload_area')

document.on('click', 'form .attach_icon', function(e, link) {
  if (!e.isMiddleClick()) {
    link.up('form').down('.upload_area').forceShow().highlight()
    e.stop()
  }
})

// toggle between hidden time tracking input and a link to show it
hideBySelector('form .hours_field')

document.on('click', 'form .add_hours_icon', function(e, link) {
  link.up('form').down('.hours_field').forceShow().down('input').focus()
  e.stop()
})

// Open links inside Comments and Notes textilized areas in new windows
document.on('mouseover', '.textilized a', function(e, link) {
  link.writeAttribute("target", "_blank");
});

hideBySelector('#activities .thread form.new_comment .extra')

document.on('focusin', '#activities .thread form.new_comment textarea', function(e, input) {
  input.up('form').down('.extra').forceShow()
})

// document.on('focusout', '.thread form.new_comment textarea', function(e, input) {
//   if (input.getValue().empty()) {
//     input.up('form').down('.extra').hide()
//   }
// })

// enable username autocompletion for main textarea in comment forms
document.on('focusin', 'form textarea[name*="[body]"]', function(e, input) {
  var form = e.findElement('form'),
      project = form.readAttribute('data-project-id')

  // projects index page has a global comment box with projects selector
  if (!project) {
    var projectSelect = form.down('select[name=project_id]')
    if (projectSelect) project = projectSelect.getValue()
  }

  if (project) {
    var people = _people_autocomplete[project],
        autocompleter = input.retrieve('autocompleter')

    if (autocompleter) {
      // update options array in case the projects selector changed value
      autocompleter.options.array = people
    } else {
      var container = new Element('div', { 'class': 'autocomplete' }).hide()
      input.insert({ after: container })
      autocompleter = new Autocompleter.Local(input, container, people, { tokens:[' '] })
      input.store('autocompleter', autocompleter)
    }
  }
})
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

FOAModel({
  name: 'LabelArrayProperty',
  extendsModel: 'StringArrayProperty',

  help: "An array of String values, taken from labels.",

  properties: [
    {
      name: 'preSet',
      defaultValue: function(oldValue, v) {
        return Array.isArray(v) ? v :
          ! v ? undefined :
          Array.isArray(oldValue) ? oldValue.binaryInsert(v) :
          [v];
      }
    },
    {
      name: 'compareProperty',
      defaultValue: function(o1, o2) {
        o1 = o1.length ? o1[o1.length-1] : 0;
        o2 = o2.length ? o2[o2.length-1] : 0;
        return o1 === o2 ? 0 : o1 > o2 ? 1 : -1;
      }
    }
  ]
});

var labelToProperty = {
  App:          'app',
  Type:         'type',
  Pri:          'pri',
  Priority:     'priority',
  Milestone:    'milestone',
  Mstone:       'milestone',
  M:            'milestone',
  Cr:           'cr',
  Iteration:    'iteration',
  ReleaseBlock: 'releaseBlock',
  OS:           'OS',
  MovedFrom:    'movedFrom'
};

var propertyLabels_ = {};

function isPropertyLabel(l) {
  if ( l in propertyLabels_ ) return propertyLabels_[l];

  var keyValue = l.match(/([^\-]*)\-(.*)/) || l.match(/(\D*)(.*)/);
  if ( keyValue ) {
    var key   = labelToProperty[keyValue[1]];
    var value = keyValue[2];

    if ( key ) {
      var kv = [key, value.intern()];
      propertyLabels_[l] = kv;
      return kv;
    }
  }

  return propertyLabels_[l] = false;
}

var QIssue = FOAM({
  model_: 'Model',
  extendsModel: 'GeneratedQIssue',

  name: 'QIssue',

  tableProperties: [
    'starred',
    'id',
    //      'app',
    'priority',
    'milestone',
    'iteration',
    'releaseBlock',
    'cr',
    'status',
    'owner',
    'summary',
    'OS',
    'modified'
  ],

  ids: ['id'],

  properties: [
    {
      model_: 'IntProperty',
      name: 'id',
      shortName: 'i',
      label: 'ID',
      required: true,
      tableWidth: '48px',
      tableFormatter: function(value, row, table) {
        var id = table.nextID();

        table.on('mouseover', function(e) {
          table.browser.preview(e, value);
        }, id);

        return '
' + value + '
';
      }
    },
    {
      name: 'author',
      preSet: function(_, a) { return a.intern(); },
      aliases: ['reporter']
    },
    {
      model_: 'StringProperty',
      name: 'priority',
      shortName: 'p',
      aliases: ['pr', 'prior'],
      tableLabel: 'Priority',
      tableWidth: '60px',
      compareProperty: function(p1, p2) {
        var priorities = ['Low', 'Medium', 'High', 'Critical'];
        var r = priorities.indexOf(p1) - priorities.indexOf(p2);
        return r === 0 ? 0 : r < 0 ? -1 : 1;
      },
      required: true
    },
    {
      model_: 'IntProperty',
      name: 'pri',
      tableLabel: 'Pri',
      tableWidth: '60px'
    },
    {
      model_: 'StringProperty',
      name: 'app',
      tableWidth: '70px'
    },
    {
      model_: 'StringProperty',
      name: 'type',
      tableWidth: '70px'
    },
    {
      model_: 'LabelArrayProperty',
      name: 'milestone',
      shortName: 'm',
      aliases: ['mstone'],
      tableLabel: 'M',
      tableWidth: '70px'
    },
    {
      model_: 'LabelArrayProperty',
      name: 'iteration',
      shortName: 'it',
      aliases: ['iter'],
      tableWidth: '69px'
    },
    {
      name: 'releaseBlock',
      shortName: 'rb',
      aliases: ['rBlock', 'release'],
      type: 'String',
      tableWidth: '103px',
      defaultValue: ''
    },
    {
      name: 'cr',
      shortName: 'c',
      aliases: ['cat', 'cr'],
      label: 'Cr',
      tableWidth: '87px',
      type: 'String',
      defaultValue: ''
    },
    {
      name: 'status',
      shortName: 's',
      aliases: ['stat'],
      type: 'String',
      preSet: function(_, s) { return s.capitalize(); },
      tableWidth: '58px',
      autocompleter: 'StatusCompleter',
      defaultValue: ''
    },
    {
      model_: 'StringArrayProperty',
      name: 'cc',
      autocompleter: 'PersonCompleter',
      displayWidth: 70,
      preSet: function(_, a) { return a.intern(); }
    },
    {
      name: 'owner',
      shortName: 'o',
      tableWidth: '181px',
      type: 'String',
      autocompleter: 'PersonCompleter',
      preSet: function(_, a) { return a.intern(); }
    },
    {
      name: 'summary',
      shortName: 'su',
      label: 'Summary',
      type: 'String',
      tableWidth: '350px',
      displayWidth: 70,
      //          tableWidth: '100%'
      tableFormatter: function(value, row, tableView) {
        return tableView.strToHTML(value);
      }
    },
    {
      name: 'description',
      displayHeight: 20,
      displayWidth: 70
    },
    {
      name: 'summaryPlusLabels',
      label: 'Summary + Labels',
      tableLabel: 'Summary + Labels',
      type: 'String',
      tableWidth: '100%',
      tableFormatter: function(value, row, tableView) {
        return tableView.strToHTML(row.summary) +
          QIssue.LABELS.tableFormatter(row.labels, row);
      }
    },
    {
      name: 'OS',
      tableWidth: '61px',
      type: 'String'
    },
    {
      model_: 'BooleanProperty',
      name: 'blocked',
      tableWidth: '20px',
      getter: function() { return !! this.blockedOn.length; }
    },
    {
      model_: 'DateTimeProperty',
      name: 'modified',
      shortName: 'mod',
      mode: 'read-write',
      required: true,
      tableWidth: '100px',
      factory: function() { return new Date(); }
    },
    {
      name: 'updated',
      hidden: true,
      setter: function(v) { this.modified = v; }
    },
    {
      model_: 'BooleanProperty',
      name: 'starred',
      tableLabel: '',
      tableWidth: '18px',
      tableFormatter: function(val, obj, tableView) {
        var view = CSSImageBooleanView.create({
          className: 'star-image',
          data: obj.starred
        });

        view.data$.addListener(function() {
          var tmp = obj.clone();
          tmp.starred = view.data;
          tableView.browser.IssueDAO.put(tmp);
        });

        tableView.addChild(view);
        return view.toHTML();
      },
      view: { model_: 'CSSImageBooleanView', className: 'star-image' },
      help: 'Whether the authenticated user has starred this issue.'
    },
    {
      model_: 'IntProperty',
      name: 'stars',
      tableWidth: '20px',
      help: 'Number of stars this issue has.'
    },
    {
      name: 'labels',
      shortName: 'l',
      aliases: ['label'],
      type: 'String',
      view: 'QIssueLabelsView',
      autocompleter: 'LabelCompleter',
      tableFormatter: function(a, row) {
        var s = '';
        for ( var i = 0 ; i < a.length ; i++ ) {
          if ( ! isPropertyLabel(a[i]) ) {
            s += ' ' + a[i] + '';
          }
        }
        return s;
      },
      postSet: function(_, a) {
        for ( var i = 0 ; i < a.length ; i++ ) {
          var kv = isPropertyLabel(a[i]);
          if ( kv ) {
            this[kv[0]] = kv[1];
            // Don't remove from labels because then label:X searches don't work.
            // a.splice(i,1);
            // i--;
          } else {
            a[i] = a[i].intern();
          }
        }
      }
    },
    {
      model_: 'LabelArrayProperty',
      name: 'movedFrom',
      tableWidth: '100px',
      preSet: function(_, v) {
        if ( Array.isArray(v) ) return v;
        if ( ! v ) return undefined;

        return (this.movedFrom || []).binaryInsert(v.charAt(0) == 'M' ? parseInt(v.substring(1)) : parseInt(v));
      }
    },
    {
      name: 'movedTo',
      preSet: function(_, v) {
        // TODO: looks like we're getting bad values, investigate and fix
        /*
          if ( v.length ) console.log('movedTo: ', v);
          return v;
        */
        return [];
      }
    }
  ],

  methods: {
    isOpen: function() {
      return !! ({
        'New':       true,
        'Accepted':  true,
        'Started':   true,
        'Untriaged': true
      }[this.status]);
    },
    writeActions: function(other, out) {
      var diff = this.diff(other);
      delete diff.starred;

      if ( Object.keys(diff).length == 0 ) return;

      function convertArray(key) {
        if ( ! diff[key] ) {
          diff[key] = [];
          return;
        }

        var delta = diff[key].added;
        for ( var i = 0; i < diff[key].removed.length; i++ )
          delta.push("-" + diff[key].removed[i]);
        diff[key] = delta;
      }

      convertArray('blockedOn');
      convertArray('blocking');
      convertArray('cc');
      convertArray('labels');

      function propToLabel(prop, label) {
        if ( diff[prop] ) {
          diff.labels = diff.labels.concat(
            '-' + label + '-' + other[prop],
            label + '-' + diff[prop]);
          delete diff[prop];
        }
      }

      propToLabel('priority', 'Priority');
      propToLabel('app', 'App');
      propToLabel('milestone', 'Milestone');
      propToLabel('cr', 'Cr');
      propToLabel('iteration', 'Iteration');
      propToLabel('releaseBlock', 'ReleaseBlock');
      propToLabel('OS', 'OS');

      var comment = QIssueComment.create({
        issueId: this.id,
        updates: QIssueCommentUpdate.create(diff)
      });

      out(comment);
    },
    newComment: function() {
      return QIssueComment.create({
        issueId: this.id,
        updates: QIssueCommentUpdate.create({
          labels: this.labels.clone(),
          owner: this.owner,
          blockedOn: this.blockedOn.clone(),
          blocking: this.blocking.clone(),
          cc: this.cc.clone(),
          mergedInto: this.mergedInto,
          status: this.status,
          summary: this.summary
        })
      })
    }
  }
});

QIssue.getPrototype();

QIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
});

GeneratedQIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
  if ( ! p["tableWidth"] ) {
    p["tableWidth"] = '80px;'
  }
});

QIssue.LABELS.toMQL = function() { return 'label'; };
<commit_msg>Fix serialization of milestone and iteration properties.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

FOAModel({
  name: 'LabelArrayProperty',
  extendsModel: 'StringArrayProperty',

  help: "An array of String values, taken from labels.",

  properties: [
    {
      name: 'preSet',
      defaultValue: function(oldValue, v) {
        return Array.isArray(v) ? v :
          ! v ? undefined :
          Array.isArray(oldValue) ? oldValue.binaryInsert(v) :
          [v];
      }
    },
    {
      name: 'compareProperty',
      defaultValue: function(o1, o2) {
        o1 = o1.length ? o1[o1.length-1] : 0;
        o2 = o2.length ? o2[o2.length-1] : 0;
        return o1 === o2 ? 0 : o1 > o2 ? 1 : -1;
      }
    }
  ]
});

var labelToProperty = {
  App:          'app',
  Type:         'type',
  Pri:          'pri',
  Priority:     'priority',
  Milestone:    'milestone',
  Mstone:       'milestone',
  M:            'milestone',
  Cr:           'cr',
  Iteration:    'iteration',
  ReleaseBlock: 'releaseBlock',
  OS:           'OS',
  MovedFrom:    'movedFrom'
};

var propertyLabels_ = {};

function isPropertyLabel(l) {
  if ( l in propertyLabels_ ) return propertyLabels_[l];

  var keyValue = l.match(/([^\-]*)\-(.*)/) || l.match(/(\D*)(.*)/);
  if ( keyValue ) {
    var key   = labelToProperty[keyValue[1]];
    var value = keyValue[2];

    if ( key ) {
      var kv = [key, value.intern()];
      propertyLabels_[l] = kv;
      return kv;
    }
  }

  return propertyLabels_[l] = false;
}

var QIssue = FOAM({
  model_: 'Model',
  extendsModel: 'GeneratedQIssue',

  name: 'QIssue',

  tableProperties: [
    'starred',
    'id',
    //      'app',
    'priority',
    'milestone',
    'iteration',
    'releaseBlock',
    'cr',
    'status',
    'owner',
    'summary',
    'OS',
    'modified'
  ],

  ids: ['id'],

  properties: [
    {
      model_: 'IntProperty',
      name: 'id',
      shortName: 'i',
      label: 'ID',
      required: true,
      tableWidth: '48px',
      tableFormatter: function(value, row, table) {
        var id = table.nextID();

        table.on('mouseover', function(e) {
          table.browser.preview(e, value);
        }, id);

        return '
' + value + '
';
      }
    },
    {
      name: 'author',
      preSet: function(_, a) { return a.intern(); },
      aliases: ['reporter']
    },
    {
      model_: 'StringProperty',
      name: 'priority',
      shortName: 'p',
      aliases: ['pr', 'prior'],
      tableLabel: 'Priority',
      tableWidth: '60px',
      compareProperty: function(p1, p2) {
        var priorities = ['Low', 'Medium', 'High', 'Critical'];
        var r = priorities.indexOf(p1) - priorities.indexOf(p2);
        return r === 0 ? 0 : r < 0 ? -1 : 1;
      },
      required: true
    },
    {
      model_: 'IntProperty',
      name: 'pri',
      tableLabel: 'Pri',
      tableWidth: '60px'
    },
    {
      model_: 'StringProperty',
      name: 'app',
      tableWidth: '70px'
    },
    {
      model_: 'StringProperty',
      name: 'type',
      tableWidth: '70px'
    },
    {
      model_: 'LabelArrayProperty',
      name: 'milestone',
      shortName: 'm',
      aliases: ['mstone'],
      tableLabel: 'M',
      tableWidth: '70px'
    },
    {
      model_: 'LabelArrayProperty',
      name: 'iteration',
      shortName: 'it',
      aliases: ['iter'],
      tableWidth: '69px'
    },
    {
      name: 'releaseBlock',
      shortName: 'rb',
      aliases: ['rBlock', 'release'],
      type: 'String',
      tableWidth: '103px',
      defaultValue: ''
    },
    {
      name: 'cr',
      shortName: 'c',
      aliases: ['cat', 'cr'],
      label: 'Cr',
      tableWidth: '87px',
      type: 'String',
      defaultValue: ''
    },
    {
      name: 'status',
      shortName: 's',
      aliases: ['stat'],
      type: 'String',
      preSet: function(_, s) { return s.capitalize(); },
      tableWidth: '58px',
      autocompleter: 'StatusCompleter',
      defaultValue: ''
    },
    {
      model_: 'StringArrayProperty',
      name: 'cc',
      autocompleter: 'PersonCompleter',
      displayWidth: 70,
      preSet: function(_, a) { return a.intern(); }
    },
    {
      name: 'owner',
      shortName: 'o',
      tableWidth: '181px',
      type: 'String',
      autocompleter: 'PersonCompleter',
      preSet: function(_, a) { return a.intern(); }
    },
    {
      name: 'summary',
      shortName: 'su',
      label: 'Summary',
      type: 'String',
      tableWidth: '350px',
      displayWidth: 70,
      //          tableWidth: '100%'
      tableFormatter: function(value, row, tableView) {
        return tableView.strToHTML(value);
      }
    },
    {
      name: 'description',
      displayHeight: 20,
      displayWidth: 70
    },
    {
      name: 'summaryPlusLabels',
      label: 'Summary + Labels',
      tableLabel: 'Summary + Labels',
      type: 'String',
      tableWidth: '100%',
      tableFormatter: function(value, row, tableView) {
        return tableView.strToHTML(row.summary) +
          QIssue.LABELS.tableFormatter(row.labels, row);
      }
    },
    {
      name: 'OS',
      tableWidth: '61px',
      type: 'String'
    },
    {
      model_: 'BooleanProperty',
      name: 'blocked',
      tableWidth: '20px',
      getter: function() { return !! this.blockedOn.length; }
    },
    {
      model_: 'DateTimeProperty',
      name: 'modified',
      shortName: 'mod',
      mode: 'read-write',
      required: true,
      tableWidth: '100px',
      factory: function() { return new Date(); }
    },
    {
      name: 'updated',
      hidden: true,
      setter: function(v) { this.modified = v; }
    },
    {
      model_: 'BooleanProperty',
      name: 'starred',
      tableLabel: '',
      tableWidth: '18px',
      tableFormatter: function(val, obj, tableView) {
        var view = CSSImageBooleanView.create({
          className: 'star-image',
          data: obj.starred
        });

        view.data$.addListener(function() {
          var tmp = obj.clone();
          tmp.starred = view.data;
          tableView.browser.IssueDAO.put(tmp);
        });

        tableView.addChild(view);
        return view.toHTML();
      },
      view: { model_: 'CSSImageBooleanView', className: 'star-image' },
      help: 'Whether the authenticated user has starred this issue.'
    },
    {
      model_: 'IntProperty',
      name: 'stars',
      tableWidth: '20px',
      help: 'Number of stars this issue has.'
    },
    {
      name: 'labels',
      shortName: 'l',
      aliases: ['label'],
      type: 'String',
      view: 'QIssueLabelsView',
      autocompleter: 'LabelCompleter',
      tableFormatter: function(a, row) {
        var s = '';
        for ( var i = 0 ; i < a.length ; i++ ) {
          if ( ! isPropertyLabel(a[i]) ) {
            s += ' ' + a[i] + '';
          }
        }
        return s;
      },
      postSet: function(_, a) {
        for ( var i = 0 ; i < a.length ; i++ ) {
          var kv = isPropertyLabel(a[i]);
          if ( kv ) {
            this[kv[0]] = kv[1];
            // Don't remove from labels because then label:X searches don't work.
            // a.splice(i,1);
            // i--;
          } else {
            a[i] = a[i].intern();
          }
        }
      }
    },
    {
      model_: 'LabelArrayProperty',
      name: 'movedFrom',
      tableWidth: '100px',
      preSet: function(_, v) {
        if ( Array.isArray(v) ) return v;
        if ( ! v ) return undefined;

        return (this.movedFrom || []).binaryInsert(v.charAt(0) == 'M' ? parseInt(v.substring(1)) : parseInt(v));
      }
    },
    {
      name: 'movedTo',
      preSet: function(_, v) {
        // TODO: looks like we're getting bad values, investigate and fix
        /*
          if ( v.length ) console.log('movedTo: ', v);
          return v;
        */
        return [];
      }
    }
  ],

  methods: {
    isOpen: function() {
      return !! ({
        'New':       true,
        'Accepted':  true,
        'Started':   true,
        'Untriaged': true
      }[this.status]);
    },
    writeActions: function(other, out) {
      var diff = this.diff(other);
      delete diff.starred;

      if ( Object.keys(diff).length == 0 ) return;

      function convertArray(key) {
        if ( ! diff[key] ) {
          diff[key] = [];
          return;
        }

        var delta = diff[key].added;
        for ( var i = 0; i < diff[key].removed.length; i++ )
          delta.push("-" + diff[key].removed[i]);
        diff[key] = delta;
      }

      convertArray('blockedOn');
      convertArray('blocking');
      convertArray('cc');
      convertArray('labels');
      convertArray('milestone');
      convertArray('iteration');

      function propToLabel(prop, label) {
        if ( diff[prop] ) {
          if ( Array.isArray(diff[prop]) ) {
            for ( var i = 0; i < diff[prop].length; i++) {
              diff.labels.push(label + '-' + diff[prop][i]);
            }
          } else {
            diff.labels = diff.labels.concat(
              '-' + label + '-' + other[prop],
              label + '-' + diff[prop]);
          }
          delete diff[prop];
        }
      }

      propToLabel('priority', 'Priority');
      propToLabel('app', 'App');
      propToLabel('milestone', 'Milestone');
      propToLabel('cr', 'Cr');
      propToLabel('iteration', 'Iteration');
      propToLabel('releaseBlock', 'ReleaseBlock');
      propToLabel('OS', 'OS');

      var comment = QIssueComment.create({
        issueId: this.id,
        updates: QIssueCommentUpdate.create(diff)
      });

      out(comment);
    },
    newComment: function() {
      return QIssueComment.create({
        issueId: this.id,
        updates: QIssueCommentUpdate.create({
          labels: this.labels.clone(),
          owner: this.owner,
          blockedOn: this.blockedOn.clone(),
          blocking: this.blocking.clone(),
          cc: this.cc.clone(),
          mergedInto: this.mergedInto,
          status: this.status,
          summary: this.summary
        })
      })
    }
  }
});

QIssue.getPrototype();

QIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
});

GeneratedQIssue.properties.forEach(function(p) {
  if ( ! p["tableFormatter"] ) {
    p["tableFormatter"] = function(v) {
      return v || '----';
    };
  }
  if ( ! p["tableWidth"] ) {
    p["tableWidth"] = '80px;'
  }
});

QIssue.LABELS.toMQL = function() { return 'label'; };
<|endoftext|>