<commit_before>(function () {

    var osdResource = angular.module('osdResource', [
        'ngLodash'
    ]);

    /*
     Creates a default resource. Generally, we would decorate this with a service that
     handles data returned from an API (for example, we could decorate this with a
     cache decorator). Each resource is built using a resourceConfig constant.
     */
    function createResource(config) {
        return function ($resource) {
            var self = this;

            self.config = config;

            var customResources = {
                query: {method: 'GET', isArray: false},
                update: {method: 'PUT'}
            };

            self.resource = $resource(config.route, {id: '@id'}, customResources);

            self.save = function (data) {
                return self.resource.save(data).$promise;
            };

            self.update = function (data) {
                return self.resource.update(data).$promise;
            };

            self.get = function (params) {
                return self.resource.get(params).$promise;
            };

            self.query = function (params) {
                return self.resource.query(params).$promise;
            };

            self.delete = function (id) {
                return self.resource.delete({id: id}).$promise;
            };

            return self;
        };
    }

    /*
       A single cache decorator is used to cache data for all resources. Each resource caches its
       data in self.caches[<resource name>].

      @ngInject
     */
    function cacheDecorator($delegate, lodash) {
        var self = this;

        self.caches = {};

        function initResourceCache($delegate) {
            self.caches[$delegate.config.name] = {
                get: {
                    cached: false,
                    params: null,
                    data: null
                },
                query: {
                    cached: false,
                    params: null,
                    data: null
                }
            };

            return self.caches[$delegate.config.name];
        }

        /*
         If not forced, cached is true and the query params are the same
         return the cached data, otherwise make the API call.
         */
        function getCachedCall(call, params, forced) {
            var currentCache = self.caches[$delegate.config.name];

            if (!currentCache) {
                currentCache = initResourceCache($delegate);
            }

            if (!forced && currentCache[call].cached && lodash.isEqual(params, currentCache[call].params)) {
                return currentCache[call].data;
            }

            /*
             Here we cache the results and set flags for the next
             time the resource method is called.
             */
            currentCache[call].cached = true;
            currentCache[call].params = params;

            /*
              This is the decorated call.
             */
            var promisedResponse = $delegate[call](params)
                .then(function (response) {
                    return response;
                });

            currentCache[call].data = promisedResponse;

            return promisedResponse;
        }

        /*
         On save or update, we invalidate the cache. This prevents us
         from returning outdated data on a later call.
         */
        function clearCachedCall(call, data) {
            var currentCache = self.caches[$delegate.config.name];

            if (!currentCache) {
                initResourceCache($delegate);
            }

            currentCache.get.cached = false;
            currentCache.query.cached = false;

            return $delegate[call](data);
        }

        return {
            // Call the parent save function and invalidate cache
            save: function (data) {
                return clearCachedCall('save', data);
            },

            // Call the parent update function and invalidate cache
            update: function (data) {
                return clearCachedCall('update', data);
            },

            // Get possibly cached data
            get: function (params, forced) {
                return getCachedCall('get', params, forced);
            },

            // Query possibly cached data
            query: function (params, forced) {
                return getCachedCall('query', params, forced);
            },

            // Call the parent delete function and invalidate cache
            delete: function (data) {
                return clearCachedCall('delete', data);
            }
        };
    }

    /*
     This provider allows separate modules to configure the resource
     generator.
     */
    osdResource.provider('ResourceConfig', function () {
        var config = [];

        return {
            config: function (value) {
                config = value;
            },

            $get: function () {
                return config;
            }
        };
    });

    /*
     Bind $provide to the module so that it can be used
     during the angular.run phase. Resource creation needs to happen in the
     angular.run phase because configuration isn't available before then.

     @ngInject
     */
    osdResource.config(function ($provide) {
        osdResource.register = {
            factory: $provide.factory,
            decorator: $provide.decorator
        };
    });

    /*
     Loop through each resource defined in resourceConfig,
     create resources and adding decorators if specified.

     @ngInject
     */
    osdResource.run(function (ResourceConfig) {
        ResourceConfig.forEach(function (config) {
            osdResource.register.factory(config.name, ['$resource', createResource(config)]);

            config.decorators.forEach(function (decorator) {
                if (decorator == 'cache') {
                    osdResource.register.decorator(config.name, cacheDecorator);
                }
            });
        });
    });
})
();
<commit_msg>Fix bug in cache clearing.<commit_after>(function () {

    var osdResource = angular.module('osdResource', [
        'ngLodash'
    ]);

    /*
     Creates a default resource. Generally, we would decorate this with a service that
     handles data returned from an API (for example, we could decorate this with a
     cache decorator). Each resource is built using a resourceConfig constant.
     */
    function createResource(config) {
        return function ($resource) {
            var self = this;

            self.config = config;

            var customResources = {
                query: {method: 'GET', isArray: false},
                update: {method: 'PUT'}
            };

            self.resource = $resource(config.route, {id: '@id'}, customResources);

            self.save = function (data) {
                return self.resource.save(data).$promise;
            };

            self.update = function (data) {
                return self.resource.update(data).$promise;
            };

            self.get = function (params) {
                return self.resource.get(params).$promise;
            };

            self.query = function (params) {
                return self.resource.query(params).$promise;
            };

            self.delete = function (id) {
                return self.resource.delete({id: id}).$promise;
            };

            return self;
        };
    }

    /*
       A single cache decorator is used to cache data for all resources. Each resource caches its
       data in self.caches[<resource name>].

      @ngInject
     */
    function cacheDecorator($delegate, lodash) {
        var self = this;

        self.caches = {};

        function initResourceCache($delegate) {
            self.caches[$delegate.config.name] = {
                get: {
                    cached: false,
                    params: null,
                    data: null
                },
                query: {
                    cached: false,
                    params: null,
                    data: null
                }
            };

            return self.caches[$delegate.config.name];
        }

        /*
         If not forced, cached is true and the query params are the same
         return the cached data, otherwise make the API call.
         */
        function getCachedCall(call, params, forced) {
            var currentCache = self.caches[$delegate.config.name];

            if (!currentCache) {
                currentCache = initResourceCache($delegate);
            }

            if (!forced && currentCache[call].cached && lodash.isEqual(params, currentCache[call].params)) {
                return currentCache[call].data;
            }

            /*
             Here we cache the results and set flags for the next
             time the resource method is called.
             */
            currentCache[call].cached = true;
            currentCache[call].params = params;

            /*
              This is the decorated call.
             */
            var promisedResponse = $delegate[call](params)
                .then(function (response) {
                    return response;
                });

            currentCache[call].data = promisedResponse;

            return promisedResponse;
        }

        /*
         On save or update, we invalidate the cache. This prevents us
         from returning outdated data on a later call.
         */
        function clearCachedCall(call, data) {
            var currentCache = self.caches[$delegate.config.name];

            if (!currentCache) {
                currentCache = initResourceCache($delegate);
            }

            currentCache.get.cached = false;
            currentCache.query.cached = false;

            return $delegate[call](data);
        }

        return {
            // Call the parent save function and invalidate cache
            save: function (data) {
                return clearCachedCall('save', data);
            },

            // Call the parent update function and invalidate cache
            update: function (data) {
                return clearCachedCall('update', data);
            },

            // Get possibly cached data
            get: function (params, forced) {
                return getCachedCall('get', params, forced);
            },

            // Query possibly cached data
            query: function (params, forced) {
                return getCachedCall('query', params, forced);
            },

            // Call the parent delete function and invalidate cache
            delete: function (data) {
                return clearCachedCall('delete', data);
            }
        };
    }

    /*
     This provider allows separate modules to configure the resource
     generator.
     */
    osdResource.provider('ResourceConfig', function () {
        var config = [];

        return {
            config: function (value) {
                config = value;
            },

            $get: function () {
                return config;
            }
        };
    });

    /*
     Bind $provide to the module so that it can be used
     during the angular.run phase. Resource creation needs to happen in the
     angular.run phase because configuration isn't available before then.

     @ngInject
     */
    osdResource.config(function ($provide) {
        osdResource.register = {
            factory: $provide.factory,
            decorator: $provide.decorator
        };
    });

    /*
     Loop through each resource defined in resourceConfig,
     create resources and adding decorators if specified.

     @ngInject
     */
    osdResource.run(function (ResourceConfig) {
        ResourceConfig.forEach(function (config) {
            osdResource.register.factory(config.name, ['$resource', createResource(config)]);

            config.decorators.forEach(function (decorator) {
                if (decorator == 'cache') {
                    osdResource.register.decorator(config.name, cacheDecorator);
                }
            });
        });
    });
})
();
<|endoftext|>


<commit_before>'use strict';

/* global describe it */

// core modules
var assert = require('assert');

// local modules
var linestream = require('../../lib/streams/linestream.js');
var sequence = require('../../lib/consumers/sequence.js');
var single = require('../../lib/consumers/single.js');

describe('sequence', function() {
  it('sequence(a, b, c) accepts abc', function() {
    var stream = linestream('test', 'abc');
    var consumer = sequence(
      single('a'),
      single('b'),
      single('c')
    );

    var parseResult = consumer(stream);

    assert.deepEqual(
      parseResult.value,
      ['a', 'b', 'c']
    );
  });

  // TODO: Invalidity propagates
});
<commit_msg>Invalid sequence test<commit_after>'use strict';

/* global describe it */

// core modules
var assert = require('assert');

// local modules
var invalid = require('../../lib/consumers/invalid.js');
var LineStream = require('../../lib/streams/LineStream.js');
var sequence = require('../../lib/consumers/sequence.js');
var single = require('../../lib/consumers/single.js');

describe('sequence', function() {
  it('sequence(a, b, c) accepts abc', function() {
    var stream = LineStream('test', 'abc');
    var consumer = sequence(
      single('a'),
      single('b'),
      single('c')
    );

    var parseResult = consumer(stream);

    assert.deepEqual(
      parseResult.value,
      ['a', 'b', 'c']
    );
  });

  it('sequence(a, invalid(b), c, invalid(d))(abcd) is invalidated by b and d', function() {
    var stream = LineStream('test', 'abcd');

    var consumer = sequence(
      single('a'),
      invalid(single('b')),
      single('c'),
      invalid(single('d'))
    );

    var parseResult = consumer(stream);

    assert.equal(parseResult.accepted, true);
    assert.equal(parseResult.valid, false);

    assert.deepEqual(parseResult.value, ['a', 'b', 'c', 'd']);

    assert.deepEqual(
      parseResult.invalidations.map(function(invalidation) {
        return [invalidation.reason, invalidation.ref.value];
      }),
      [
        ['Element invalid.', 'b'],
        ['Element invalid.', 'd']
      ]
    );
  });
});
<|endoftext|>


<commit_before>/*eslint strict:0, no-console:0*/
'use strict';

const _ = require('lodash');
const cheerio = require('cheerio');
const npm = require('npm');
const Promise = require('bluebird');
const { all, fromNode, promisify, promisifyAll } = require('bluebird');
const fs = promisifyAll(require('fs'));
const request = require('request');
const logOutput = function (...args) { console.log(...args); };
const logProgress = function (...args) { console.error(...args); };

function calculateDownloadMetrics(extensionInfo) {
  let downloadsArray = extensionInfo.downloads;
  // downloadsLastWeek
  let today = new Date();
  today.setDate(today.getDate() - 7);
  let weekAgo = today.toISOString().substring(0, 10);
  extensionInfo.downloadsLastWeek = downloadsArray
    .filter(obj => obj.day >= weekAgo)
    .reduce((sum, obj) => sum + obj.downloads, 0);
  // downloadsTotal
  extensionInfo.downloadsTotal = downloadsArray
    .reduce((sum, obj) => sum + obj.downloads, 0);
}

function buildRegistry(targetFile) {

  logProgress('loading npm');
  return fromNode(npm.load.bind(npm))
    .then(() => {
      // npm is loaded, we can start the search
      // get all entries tagged 'brackets-extension'
      logProgress(`executing npm search brackets-extension`);
      return fromNode(npm.commands.search.bind(npm.commands, ['brackets-extension'], true));
    })
    .then(searchResults => {
      // call view for all potential extensions
      logProgress(`executing npm view to get detailed info about the extensions`);
      let npmView = promisify(npm.commands.view, npm.commands);
      return all(Object.keys(searchResults).map(
        extensionId =>
          npmView([extensionId + '@latest'], true).then(result =>
            result[Object.keys(result)[0]]
          )
      ));
    })
    .then(viewResults => {
      logProgress(`got all view results`);
      // filter out those, which doesn't have brackets engine specified
      return viewResults.filter(result => result.engines && result.engines.brackets);
    })
    .then(extensionInfos => {
      logProgress(`getting download info counts for the extensions`);
      // get download counts for the extensions
      let extensionIds = extensionInfos.map(i => i.name);
      let from = '2015-01-01';
      let to = new Date().toISOString().substring(0, 10);
      return new Promise((resolve, reject) => {
        request(`https://api.npmjs.org/downloads/range/${from}:${to}/${extensionIds.join(',')}`,
        (error, response, body) => {

          if (error) {
            return reject(error);
          }

          if (response.statusCode !== 200) {
            return reject(body);
          }

          if (typeof body === 'string') {
            try {
              body = JSON.parse(body);
            } catch (err) {
              return reject(err);
            }
          }

          if (extensionIds.length === 1) {
            extensionInfos[0].downloads = body.downloads;
            calculateDownloadMetrics(extensionInfos[0]);
          } else {
            extensionInfos.forEach(extensionInfo => {
              let info = body[extensionInfo.name];
              if (info && info.downloads) {
                extensionInfo.downloads = info.downloads;
                calculateDownloadMetrics(extensionInfo);
              }
            });
          }

          resolve(extensionInfos);

        });
      });
    })
    .then(extensionInfos => {
      logProgress(`getting issue/pr counts for the extensions`);
      return Promise.all(extensionInfos.map(extensionInfo => {

        const githubRepo = /^https?:\/\/[^\/]*github.com\/([^\/]+)\/([^\/]+)$/;

        let githubIssueCount = -1;
        let githubPullCount = -1;

        let candidates = _.compact([
          extensionInfo.repository ? extensionInfo.repository.url : null,
          extensionInfo.repository,
          extensionInfo.homepage
        ]).filter(x => typeof x === 'string').filter(x => x.match(githubRepo));

        if (candidates.length === 0) {
          return Promise.resolve();
        }

        let m = candidates[0].match(githubRepo);
        let username = m[1];
        let repo = m[2];

        extensionInfo.githubUsername = username;
        extensionInfo.githubRepository = repo;

        if (repo.match(/\.git$/)) { repo = repo.slice(0, -4); }

        return new Promise((resolve) => {
          let url = `https://github.com/${username}/${repo}/issues/counts`;
          console.log(url);
          request({
            url,
            method: `GET`,
            json: true,
            headers: {
              'User-Agent': `brackets-npm-registry`
            }
          }, (error, response, body) => {
            if (error || response.statusCode !== 200) { return resolve(); }
            githubIssueCount = parseInt(cheerio.load(body.issues_count)('.counter').text(), 10);
            githubPullCount = parseInt(cheerio.load(body.pulls_count)('.counter').text(), 10);
            resolve();
          });
        }).then(() => {
          extensionInfo.githubIssueCount = isNaN(githubIssueCount) ? -1 : githubIssueCount;
          extensionInfo.githubPullCount = isNaN(githubPullCount) ? -1 : githubPullCount;
        });
      })).then(() => extensionInfos);
    })
    .then(extensionInfos => {
      let strResults = JSON.stringify(extensionInfos, null, 2);
      logProgress(`all done`);
      if (targetFile) {
        logProgress(`writing the results to file:\n`, targetFile);
        return fs.writeFileAsync(targetFile, strResults)
          .then(() => extensionInfos);
      }
      logOutput(strResults);
      return extensionInfos;
    });

}

if (process.argv[1] === __filename) {
  buildRegistry(...process.argv.slice(2));
} else {
  module.exports = buildRegistry;
}

<commit_msg>remove un-needed console.log<commit_after>/*eslint strict:0, no-console:0*/
'use strict';

const _ = require('lodash');
const cheerio = require('cheerio');
const npm = require('npm');
const Promise = require('bluebird');
const { all, fromNode, promisify, promisifyAll } = require('bluebird');
const fs = promisifyAll(require('fs'));
const request = require('request');
const logOutput = function (...args) { console.log(...args); };
const logProgress = function (...args) { console.error(...args); };

function calculateDownloadMetrics(extensionInfo) {
  let downloadsArray = extensionInfo.downloads;
  // downloadsLastWeek
  let today = new Date();
  today.setDate(today.getDate() - 7);
  let weekAgo = today.toISOString().substring(0, 10);
  extensionInfo.downloadsLastWeek = downloadsArray
    .filter(obj => obj.day >= weekAgo)
    .reduce((sum, obj) => sum + obj.downloads, 0);
  // downloadsTotal
  extensionInfo.downloadsTotal = downloadsArray
    .reduce((sum, obj) => sum + obj.downloads, 0);
}

function buildRegistry(targetFile) {

  logProgress('loading npm');
  return fromNode(npm.load.bind(npm))
    .then(() => {
      // npm is loaded, we can start the search
      // get all entries tagged 'brackets-extension'
      logProgress(`executing npm search brackets-extension`);
      return fromNode(npm.commands.search.bind(npm.commands, ['brackets-extension'], true));
    })
    .then(searchResults => {
      // call view for all potential extensions
      logProgress(`executing npm view to get detailed info about the extensions`);
      let npmView = promisify(npm.commands.view, npm.commands);
      return all(Object.keys(searchResults).map(
        extensionId =>
          npmView([extensionId + '@latest'], true).then(result =>
            result[Object.keys(result)[0]]
          )
      ));
    })
    .then(viewResults => {
      logProgress(`got all view results`);
      // filter out those, which doesn't have brackets engine specified
      return viewResults.filter(result => result.engines && result.engines.brackets);
    })
    .then(extensionInfos => {
      logProgress(`getting download info counts for the extensions`);
      // get download counts for the extensions
      let extensionIds = extensionInfos.map(i => i.name);
      let from = '2015-01-01';
      let to = new Date().toISOString().substring(0, 10);
      return new Promise((resolve, reject) => {
        request(`https://api.npmjs.org/downloads/range/${from}:${to}/${extensionIds.join(',')}`,
        (error, response, body) => {

          if (error) {
            return reject(error);
          }

          if (response.statusCode !== 200) {
            return reject(body);
          }

          if (typeof body === 'string') {
            try {
              body = JSON.parse(body);
            } catch (err) {
              return reject(err);
            }
          }

          if (extensionIds.length === 1) {
            extensionInfos[0].downloads = body.downloads;
            calculateDownloadMetrics(extensionInfos[0]);
          } else {
            extensionInfos.forEach(extensionInfo => {
              let info = body[extensionInfo.name];
              if (info && info.downloads) {
                extensionInfo.downloads = info.downloads;
                calculateDownloadMetrics(extensionInfo);
              }
            });
          }

          resolve(extensionInfos);

        });
      });
    })
    .then(extensionInfos => {
      logProgress(`getting issue/pr counts for the extensions`);
      return Promise.all(extensionInfos.map(extensionInfo => {

        const githubRepo = /^https?:\/\/[^\/]*github.com\/([^\/]+)\/([^\/]+)$/;

        let githubIssueCount = -1;
        let githubPullCount = -1;

        let candidates = _.compact([
          extensionInfo.repository ? extensionInfo.repository.url : null,
          extensionInfo.repository,
          extensionInfo.homepage
        ]).filter(x => typeof x === 'string').filter(x => x.match(githubRepo));

        if (candidates.length === 0) {
          return Promise.resolve();
        }

        let m = candidates[0].match(githubRepo);
        let username = m[1];
        let repo = m[2];

        extensionInfo.githubUsername = username;
        extensionInfo.githubRepository = repo;

        if (repo.match(/\.git$/)) { repo = repo.slice(0, -4); }

        return new Promise((resolve) => {
          let url = `https://github.com/${username}/${repo}/issues/counts`;
          request({
            url,
            method: `GET`,
            json: true,
            headers: {
              'User-Agent': `brackets-npm-registry`
            }
          }, (error, response, body) => {
            if (error || response.statusCode !== 200) { return resolve(); }
            githubIssueCount = parseInt(cheerio.load(body.issues_count)('.counter').text(), 10);
            githubPullCount = parseInt(cheerio.load(body.pulls_count)('.counter').text(), 10);
            resolve();
          });
        }).then(() => {
          extensionInfo.githubIssueCount = isNaN(githubIssueCount) ? -1 : githubIssueCount;
          extensionInfo.githubPullCount = isNaN(githubPullCount) ? -1 : githubPullCount;
        });
      })).then(() => extensionInfos);
    })
    .then(extensionInfos => {
      let strResults = JSON.stringify(extensionInfos, null, 2);
      logProgress(`all done`);
      if (targetFile) {
        logProgress(`writing the results to file:\n`, targetFile);
        return fs.writeFileAsync(targetFile, strResults)
          .then(() => extensionInfos);
      }
      logOutput(strResults);
      return extensionInfos;
    });

}

if (process.argv[1] === __filename) {
  buildRegistry(...process.argv.slice(2));
} else {
  module.exports = buildRegistry;
}

<|endoftext|>


<commit_before>/**
 * APP
 *
 * The app controller manages the global scope.
 */

var util = require('util'),
    events = require('events'),
    rewriter = require('../util/jsonrewriter'),
    Amount = ripple.Amount;

var module = angular.module('app', []);

module.controller('AppCtrl', ['$rootScope', '$compile', 'rpId', 'rpNetwork',
                              function ($scope, $compile, $id, $net)
{
  reset();

  var account;

  // Global reference for debugging only (!)
  if ("object" === typeof rippleclient) {
    rippleclient.id = $id;
    rippleclient.net = $net;
  }

  function reset()
  {
    $scope.account = {};
    $scope.lines = {};
    $scope.offers = {};
    $scope.events = [];
    $scope.history = [];
    $scope.balances = {};
    $scope.loadState = [];
  }

  var myHandleAccountEvent;
  var myHandleAccountEntry;

  function handleAccountLoad(e, data)
  {
    var remote = $net.remote;

    account = data.account;

    reset();

    remote.set_secret(data.account, data.secret);

    var accountObj = remote.account(data.account);

    // We need a reference to these functions after they're bound, so we can
    // unregister them if the account is unloaded.
    myHandleAccountEvent = handleAccountEvent;
    myHandleAccountEntry = handleAccountEntry;

    accountObj.on('transaction', myHandleAccountEvent);
    accountObj.on('entry', myHandleAccountEntry);

    accountObj.entry(function (err, entry) {
      if (err) {
        $scope.loadState['account'] = true;
      }
    });

    // Ripple credit lines
    remote.request_account_lines(data.account)
      .on('success', handleRippleLines)
      .on('error', handleRippleLinesError).request();

    // Transactions
    remote.request_account_tx({
      'account': data.account,
      'ledger_index_min': -1,
      'descending': true,
      'limit': Options.transactions_per_page,
      'count': true
    })
      .on('success', handleAccountTx)
      .on('error', handleAccountTxError).request();

    // Outstanding offers
    remote.request_account_offers(data.account)
      .on('success', handleOffers)
      .on('error', handleOffersError).request();
  }

  function handleAccountUnload(e, data)
  {
    var remote = $net.remote;
    var accountObj = remote.account(data.account);
    accountObj.removeListener('transaction', myHandleAccountEvent);
    accountObj.removeListener('entry', myHandleAccountEntry);
  }

  function handleRippleLines(data)
  {
    $scope.$apply(function () {
      $scope.lines = {};

      for (var n=0, l=data.lines.length; n<l; n++) {
        var line = data.lines[n];

        // XXX: This reinterpretation of the server response should be in the
        //      library upstream.
        line = $.extend({}, line, {
          limit: ripple.Amount.from_json({value: line.limit, currency: line.currency, issuer: line.account}),
          limit_peer: ripple.Amount.from_json({value: line.limit_peer, currency: line.currency, issuer: account}),
          balance: ripple.Amount.from_json({value: line.balance, currency: line.currency, issuer: account})
        });

        $scope.lines[line.account+line.currency] = line;
        updateRippleBalance(line.currency, line.account, line.balance);
      }
      console.log('lines updated:', $scope.lines);

      $scope.loadState['lines'] = true;
    });
  }

  function handleRippleLinesError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['lines'] = true;
    });
  }

  function handleOffers(data)
  {
    $scope.$apply(function () {
      data.offers.forEach(function (offerData) {
        var offer = {
          seq: +offerData.seq,
          gets: ripple.Amount.from_json(offerData.taker_gets),
          pays: ripple.Amount.from_json(offerData.taker_pays)
        };

        updateOffer(offer);
      });
      console.log('offers updated:', $scope.offers);

      $scope.loadState['offers'] = true;
    });
  }

  function handleOffersError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['offers'] = true;
    });
  }

  function handleAccountEntry(data)
  {
    var remote = $net.remote;
    $scope.$apply(function () {
      $scope.account = data;

      // As per json wire format convention, real ledger entries are CamelCase,
      // e.g. OwnerCount, additional convenience fields are lower case, e.g.
      // reserve, max_spend.
      var reserve_base = Amount.from_json(""+remote._reserve_base),
          reserve_inc  = Amount.from_json(""+remote._reserve_inc),
          owner_count  = $scope.account.OwnerCount || 0;
      $scope.account.reserve_base = reserve_base;
      $scope.account.reserve = reserve_base.add(reserve_inc.product_human(owner_count));
      $scope.account.reserve_to_add_trust = reserve_base.add(reserve_inc.product_human(owner_count+1));
      $scope.account.reserve_low_balance = $scope.account.reserve.product_human(2);

      // Maximum amount user can spend
      var bal = Amount.from_json(data.Balance);
      $scope.account.max_spend = bal.subtract($scope.account.reserve);

      $scope.loadState['account'] = true;
    });
  }

  function handleAccountTx(data)
  {
    $scope.$apply(function () {
      $scope.history_count = data.count;

      if (data.transactions) {
        data.transactions.reverse().forEach(function (e) {
          processTxn(e.tx, e.meta, true);
        });
      }

      $scope.loadState['transactions'] = true;
    });
  }
  function handleAccountTxError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['transactions'] = true;
    });
  }

  function handleAccountEvent(e)
  {
    $scope.$apply(function () {
      processTxn(e.transaction, e.meta);
    });
  }

  /**
   * Process a transaction and add it to the history table.
   */
  function processTxn(tx, meta, is_historic)
  {
    var processedTxn = rewriter.processTxn(tx, meta, account);

    if (processedTxn) {
      var transaction = processedTxn.transaction;

      // Show status notification
      if (processedTxn.tx_result === "tesSUCCESS" &&
          transaction &&
          !is_historic) {
        $scope.$broadcast('$appTxNotification', transaction);
      }

      // Add to recent notifications
      if (processedTxn.tx_result === "tesSUCCESS") {
        $scope.events.unshift(processedTxn);
      }

      // Add to history
      $scope.history.unshift(processedTxn);

      // Update Ripple lines
      if (processedTxn.effects && !is_historic) {
        updateLines(processedTxn.effects);
      }

      // Update my offers
      if (processedTxn.effects && !is_historic) {
        // Iterate on each effect to find offers
        processedTxn.effects.forEach(function (effect) {
          // Only these types are offers
          if (_.contains([
            'offer_created',
            'offer_funded',
            'offer_partially_funded',
            'offer_cancelled'], effect.type))
          {
            var offer = {
              seq: +effect.seq,
              gets: effect.gets,
              pays: effect.pays,
              deleted: effect.deleted,
              flags: processedTxn.transaction.flags
            };

            updateOffer(offer);
          }
        });
      }
    }
  }

  function updateOffer(offer)
  {
    if (offer.flags && offer.flags === ripple.Transaction.flags.OfferCreate.Sell) {
      offer.type = 'sell';
      offer.first = offer.gets;
      offer.second = offer.pays;
    } else {
      offer.type = 'buy';
      offer.first = offer.pays;
      offer.second = offer.gets;
    }

    if (!offer.deleted) {
      $scope.offers[""+offer.seq] = offer;
    } else {
      delete $scope.offers[""+offer.seq];
    }
  }

  function updateLines(effects)
  {
    if (!$.isArray(effects)) return;

    $.each(effects, function () {
      if (_.contains([
        'trust_create_local',
        'trust_create_remote',
        'trust_change_local',
        'trust_change_remote',
        'trust_change_balance'], this.type))
      {
        var effect = this,
            line = {},
            index = effect.counterparty + effect.currency;

        line.currency = effect.currency;
        line.account = effect.counterparty;

        if (effect.balance) {
          line.balance = effect.balance;
          updateRippleBalance(effect.currency,
                                    effect.counterparty,
                                    effect.balance);
        }

        if (effect.deleted) {
          delete $scope.lines[index];
          return;
        }

        if (effect.limit) {
          line.limit = effect.limit;
        }

        if (effect.limit_peer) {
          line.limit_peer = effect.limit_peer;
        }

        $scope.lines[index] = $.extend($scope.lines[index], line);
      }
    });
  }

  function updateRippleBalance(currency, new_account, new_balance)
  {
    // Ensure the balances entry exists first
    if (!$scope.balances[currency]) {
      $scope.balances[currency] = {components: {}, total: null};
    }

    var balance = $scope.balances[currency];

    if (new_account) {
      balance.components[new_account] = new_balance;
    }

    $(balance.components).sort(function(a,b){
      debugger
      return a.compareTo(b);
    });

    balance.total = null;
    for (var counterparty in balance.components) {
      var amount = balance.components[counterparty];
      balance.total = balance.total ? balance.total.add(amount) : amount;
    }
  }

  // Personalized default currency set
  if (!store.get('ripple_currencies_all')) {
    store.set('ripple_currencies_all',require('../data/currencies'));
  }

  $scope.currencies_all = store.get('ripple_currencies_all')
    ? store.get('ripple_currencies_all')
    : require('../data/currencies');

  // Personalized default pair set
  if (!store.get('ripple_pairs_all')) {
    store.set('ripple_pairs_all',require('../data/pairs'));
  }

  $scope.pairs_all = store.get('ripple_pairs_all')
    ? store.get('ripple_pairs_all')
    : require('../data/pairs');

  function compare(a, b) {
    if (a.order < b.order) return 1;
    if (a.order > b.order) return -1;
    return 0;
  }

  // sort currencies and pairs by order
  $scope.currencies_all.sort(compare);
  $scope.pairs_all.sort(compare);

  $scope.$watch('currencies_all', function(){
    store.set('ripple_currencies_all',$scope.currencies_all);
  }, true);

  $scope.$watch('pairs_all', function(){
    store.set('ripple_pairs_all',$scope.pairs_all);
  }, true);

  $scope.currencies = $scope.currencies_all.slice(1);
  $scope.pairs = $scope.pairs_all.slice(1);

  $scope.app_loaded = true
  // Enable screen
  // $('body').addClass('loaded');

  // Moved this to the run block
  // Nav links same page click fix
  // $('nav a').click(function(){
  //   if (location.hash == this.hash) {
  //     location.href="#/";
  //     location.href=this.href;
  //   }
  // });

  // Moved this to a directive
  // Add status box to DOM
  // var template = require('../../jade/client/status.jade')();
  // $compile(template)($scope, function (el, $scope) {
  //   el.appendTo('header');
  // });

  $scope.$on('$idAccountLoad', function (e, data) {
    // Server is connected
    if ($scope.connected) {
      handleAccountLoad(e, data);
    }

    // Server is not connected yet. Handle account load after server response.
    $scope.$on('$netConnected', function(){
      if ($.isEmptyObject($scope.account)) {
        handleAccountLoad(e, data);
      }
    });
  });

  $scope.$on('$idAccountUnload', handleAccountUnload);

  // XXX: The app also needs to handle updating its data when the connection is
  //      lost and later re-established. (... or will the Ripple lib do that for us?)
  var removeFirstConnectionListener =
        $scope.$on('$netConnected', handleFirstConnection);
  function handleFirstConnection() {
    removeFirstConnectionListener();
  }

  $net.listenId($id);
  $net.init();
  $id.init();

  // Testing hooks

  this.reset                  =  reset;
  this.handleAccountLoad      =  handleAccountLoad;
  this.handleAccountUnload    =  handleAccountUnload;
  this.handleRippleLines      =  handleRippleLines;
  this.handleRippleLinesError =  handleRippleLinesError;
  this.handleOffers           =  handleOffers;
  this.handleOffersError      =  handleOffersError;
  this.handleAccountEntry     =  handleAccountEntry;
  this.handleAccountTx        =  handleAccountTx;
  this.handleAccountTxError   =  handleAccountTxError;
  this.handleAccountEvent     =  handleAccountEvent;
  this.processTxn             =  processTxn;
  this.updateOffer            =  updateOffer;
  this.updateLines            =  updateLines;
  this.updateRippleBalance    =  updateRippleBalance;
  this.compare                =  compare;
  this.handleFirstConnection  =  handleFirstConnection;

}]);
<commit_msg>Remove errant debugger statement<commit_after>/**
 * APP
 *
 * The app controller manages the global scope.
 */

var util = require('util'),
    events = require('events'),
    rewriter = require('../util/jsonrewriter'),
    Amount = ripple.Amount;

var module = angular.module('app', []);

module.controller('AppCtrl', ['$rootScope', '$compile', 'rpId', 'rpNetwork',
                              function ($scope, $compile, $id, $net)
{
  reset();

  var account;

  // Global reference for debugging only (!)
  if ("object" === typeof rippleclient) {
    rippleclient.id = $id;
    rippleclient.net = $net;
  }

  function reset()
  {
    $scope.account = {};
    $scope.lines = {};
    $scope.offers = {};
    $scope.events = [];
    $scope.history = [];
    $scope.balances = {};
    $scope.loadState = [];
  }

  var myHandleAccountEvent;
  var myHandleAccountEntry;

  function handleAccountLoad(e, data)
  {
    var remote = $net.remote;

    account = data.account;

    reset();

    remote.set_secret(data.account, data.secret);

    var accountObj = remote.account(data.account);

    // We need a reference to these functions after they're bound, so we can
    // unregister them if the account is unloaded.
    myHandleAccountEvent = handleAccountEvent;
    myHandleAccountEntry = handleAccountEntry;

    accountObj.on('transaction', myHandleAccountEvent);
    accountObj.on('entry', myHandleAccountEntry);

    accountObj.entry(function (err, entry) {
      if (err) {
        $scope.loadState['account'] = true;
      }
    });

    // Ripple credit lines
    remote.request_account_lines(data.account)
      .on('success', handleRippleLines)
      .on('error', handleRippleLinesError).request();

    // Transactions
    remote.request_account_tx({
      'account': data.account,
      'ledger_index_min': -1,
      'descending': true,
      'limit': Options.transactions_per_page,
      'count': true
    })
      .on('success', handleAccountTx)
      .on('error', handleAccountTxError).request();

    // Outstanding offers
    remote.request_account_offers(data.account)
      .on('success', handleOffers)
      .on('error', handleOffersError).request();
  }

  function handleAccountUnload(e, data)
  {
    var remote = $net.remote;
    var accountObj = remote.account(data.account);
    accountObj.removeListener('transaction', myHandleAccountEvent);
    accountObj.removeListener('entry', myHandleAccountEntry);
  }

  function handleRippleLines(data)
  {
    $scope.$apply(function () {
      $scope.lines = {};

      for (var n=0, l=data.lines.length; n<l; n++) {
        var line = data.lines[n];

        // XXX: This reinterpretation of the server response should be in the
        //      library upstream.
        line = $.extend({}, line, {
          limit: ripple.Amount.from_json({value: line.limit, currency: line.currency, issuer: line.account}),
          limit_peer: ripple.Amount.from_json({value: line.limit_peer, currency: line.currency, issuer: account}),
          balance: ripple.Amount.from_json({value: line.balance, currency: line.currency, issuer: account})
        });

        $scope.lines[line.account+line.currency] = line;
        updateRippleBalance(line.currency, line.account, line.balance);
      }
      console.log('lines updated:', $scope.lines);

      $scope.loadState['lines'] = true;
    });
  }

  function handleRippleLinesError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['lines'] = true;
    });
  }

  function handleOffers(data)
  {
    $scope.$apply(function () {
      data.offers.forEach(function (offerData) {
        var offer = {
          seq: +offerData.seq,
          gets: ripple.Amount.from_json(offerData.taker_gets),
          pays: ripple.Amount.from_json(offerData.taker_pays)
        };

        updateOffer(offer);
      });
      console.log('offers updated:', $scope.offers);

      $scope.loadState['offers'] = true;
    });
  }

  function handleOffersError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['offers'] = true;
    });
  }

  function handleAccountEntry(data)
  {
    var remote = $net.remote;
    $scope.$apply(function () {
      $scope.account = data;

      // As per json wire format convention, real ledger entries are CamelCase,
      // e.g. OwnerCount, additional convenience fields are lower case, e.g.
      // reserve, max_spend.
      var reserve_base = Amount.from_json(""+remote._reserve_base),
          reserve_inc  = Amount.from_json(""+remote._reserve_inc),
          owner_count  = $scope.account.OwnerCount || 0;
      $scope.account.reserve_base = reserve_base;
      $scope.account.reserve = reserve_base.add(reserve_inc.product_human(owner_count));
      $scope.account.reserve_to_add_trust = reserve_base.add(reserve_inc.product_human(owner_count+1));
      $scope.account.reserve_low_balance = $scope.account.reserve.product_human(2);

      // Maximum amount user can spend
      var bal = Amount.from_json(data.Balance);
      $scope.account.max_spend = bal.subtract($scope.account.reserve);

      $scope.loadState['account'] = true;
    });
  }

  function handleAccountTx(data)
  {
    $scope.$apply(function () {
      $scope.history_count = data.count;

      if (data.transactions) {
        data.transactions.reverse().forEach(function (e) {
          processTxn(e.tx, e.meta, true);
        });
      }

      $scope.loadState['transactions'] = true;
    });
  }
  function handleAccountTxError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['transactions'] = true;
    });
  }

  function handleAccountEvent(e)
  {
    $scope.$apply(function () {
      processTxn(e.transaction, e.meta);
    });
  }

  /**
   * Process a transaction and add it to the history table.
   */
  function processTxn(tx, meta, is_historic)
  {
    var processedTxn = rewriter.processTxn(tx, meta, account);

    if (processedTxn) {
      var transaction = processedTxn.transaction;

      // Show status notification
      if (processedTxn.tx_result === "tesSUCCESS" &&
          transaction &&
          !is_historic) {
        $scope.$broadcast('$appTxNotification', transaction);
      }

      // Add to recent notifications
      if (processedTxn.tx_result === "tesSUCCESS") {
        $scope.events.unshift(processedTxn);
      }

      // Add to history
      $scope.history.unshift(processedTxn);

      // Update Ripple lines
      if (processedTxn.effects && !is_historic) {
        updateLines(processedTxn.effects);
      }

      // Update my offers
      if (processedTxn.effects && !is_historic) {
        // Iterate on each effect to find offers
        processedTxn.effects.forEach(function (effect) {
          // Only these types are offers
          if (_.contains([
            'offer_created',
            'offer_funded',
            'offer_partially_funded',
            'offer_cancelled'], effect.type))
          {
            var offer = {
              seq: +effect.seq,
              gets: effect.gets,
              pays: effect.pays,
              deleted: effect.deleted,
              flags: processedTxn.transaction.flags
            };

            updateOffer(offer);
          }
        });
      }
    }
  }

  function updateOffer(offer)
  {
    if (offer.flags && offer.flags === ripple.Transaction.flags.OfferCreate.Sell) {
      offer.type = 'sell';
      offer.first = offer.gets;
      offer.second = offer.pays;
    } else {
      offer.type = 'buy';
      offer.first = offer.pays;
      offer.second = offer.gets;
    }

    if (!offer.deleted) {
      $scope.offers[""+offer.seq] = offer;
    } else {
      delete $scope.offers[""+offer.seq];
    }
  }

  function updateLines(effects)
  {
    if (!$.isArray(effects)) return;

    $.each(effects, function () {
      if (_.contains([
        'trust_create_local',
        'trust_create_remote',
        'trust_change_local',
        'trust_change_remote',
        'trust_change_balance'], this.type))
      {
        var effect = this,
            line = {},
            index = effect.counterparty + effect.currency;

        line.currency = effect.currency;
        line.account = effect.counterparty;

        if (effect.balance) {
          line.balance = effect.balance;
          updateRippleBalance(effect.currency,
                                    effect.counterparty,
                                    effect.balance);
        }

        if (effect.deleted) {
          delete $scope.lines[index];
          return;
        }

        if (effect.limit) {
          line.limit = effect.limit;
        }

        if (effect.limit_peer) {
          line.limit_peer = effect.limit_peer;
        }

        $scope.lines[index] = $.extend($scope.lines[index], line);
      }
    });
  }

  function updateRippleBalance(currency, new_account, new_balance)
  {
    // Ensure the balances entry exists first
    if (!$scope.balances[currency]) {
      $scope.balances[currency] = {components: {}, total: null};
    }

    var balance = $scope.balances[currency];

    if (new_account) {
      balance.components[new_account] = new_balance;
    }

    $(balance.components).sort(function(a,b){
      return a.compareTo(b);
    });

    balance.total = null;
    for (var counterparty in balance.components) {
      var amount = balance.components[counterparty];
      balance.total = balance.total ? balance.total.add(amount) : amount;
    }
  }

  // Personalized default currency set
  if (!store.get('ripple_currencies_all')) {
    store.set('ripple_currencies_all',require('../data/currencies'));
  }

  $scope.currencies_all = store.get('ripple_currencies_all')
    ? store.get('ripple_currencies_all')
    : require('../data/currencies');

  // Personalized default pair set
  if (!store.get('ripple_pairs_all')) {
    store.set('ripple_pairs_all',require('../data/pairs'));
  }

  $scope.pairs_all = store.get('ripple_pairs_all')
    ? store.get('ripple_pairs_all')
    : require('../data/pairs');

  function compare(a, b) {
    if (a.order < b.order) return 1;
    if (a.order > b.order) return -1;
    return 0;
  }

  // sort currencies and pairs by order
  $scope.currencies_all.sort(compare);
  $scope.pairs_all.sort(compare);

  $scope.$watch('currencies_all', function(){
    store.set('ripple_currencies_all',$scope.currencies_all);
  }, true);

  $scope.$watch('pairs_all', function(){
    store.set('ripple_pairs_all',$scope.pairs_all);
  }, true);

  $scope.currencies = $scope.currencies_all.slice(1);
  $scope.pairs = $scope.pairs_all.slice(1);

  $scope.app_loaded = true
  // Enable screen
  // $('body').addClass('loaded');

  // Moved this to the run block
  // Nav links same page click fix
  // $('nav a').click(function(){
  //   if (location.hash == this.hash) {
  //     location.href="#/";
  //     location.href=this.href;
  //   }
  // });

  // Moved this to a directive
  // Add status box to DOM
  // var template = require('../../jade/client/status.jade')();
  // $compile(template)($scope, function (el, $scope) {
  //   el.appendTo('header');
  // });

  $scope.$on('$idAccountLoad', function (e, data) {
    // Server is connected
    if ($scope.connected) {
      handleAccountLoad(e, data);
    }

    // Server is not connected yet. Handle account load after server response.
    $scope.$on('$netConnected', function(){
      if ($.isEmptyObject($scope.account)) {
        handleAccountLoad(e, data);
      }
    });
  });

  $scope.$on('$idAccountUnload', handleAccountUnload);

  // XXX: The app also needs to handle updating its data when the connection is
  //      lost and later re-established. (... or will the Ripple lib do that for us?)
  var removeFirstConnectionListener =
        $scope.$on('$netConnected', handleFirstConnection);
  function handleFirstConnection() {
    removeFirstConnectionListener();
  }

  $net.listenId($id);
  $net.init();
  $id.init();

  // Testing hooks

  this.reset                  =  reset;
  this.handleAccountLoad      =  handleAccountLoad;
  this.handleAccountUnload    =  handleAccountUnload;
  this.handleRippleLines      =  handleRippleLines;
  this.handleRippleLinesError =  handleRippleLinesError;
  this.handleOffers           =  handleOffers;
  this.handleOffersError      =  handleOffersError;
  this.handleAccountEntry     =  handleAccountEntry;
  this.handleAccountTx        =  handleAccountTx;
  this.handleAccountTxError   =  handleAccountTxError;
  this.handleAccountEvent     =  handleAccountEvent;
  this.processTxn             =  processTxn;
  this.updateOffer            =  updateOffer;
  this.updateLines            =  updateLines;
  this.updateRippleBalance    =  updateRippleBalance;
  this.compare                =  compare;
  this.handleFirstConnection  =  handleFirstConnection;

}]);
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

require(["jquery","sakai/sakai.api.core"], function($, sakai) {

    sakai_global.category = sakai_global.category || {};

    sakai_global.category = function() {

        var pubdata = {};
        var privdata = {};

        // Containers
        var $exploreNavigation = $("#explore_navigation");
        var toplevelId = "";

        // Templates
        var exploreNavigationTemplate = "explore_navigation_template";

        /**
         * Create the breadcrumb data and render on screen
         * @param {Object} dirData Object that contains children for the category
         * @param {Array} bbqData Array of IDs fetched with bbq to help identify correct children
         */
        var createBreadcrumb = function(dirData, bbqData){
            // Create top level breadcrumb
            var breadcrumb = [];
            breadcrumb.push({
                "title": sakai.api.i18n.General.getValueForKey("ALL_CATEGORIES"),
                "id": bbqData[0],
                "link": true,
                "url": "/dev/allcategories.html"
            });
            breadcrumb.push({
                "title": dirData.title,
                "id": dirData.id,
                "link": bbqData.length - 1
            });
            bbqData.splice(0,1);

            // Create children level breadcrumb
            var children = dirData.children[bbqData[0]];
            $.each(bbqData, function(index, item){
                breadcrumb.push({
                    "title": children.title,
                    "id": item,
                    "link": bbqData.length - 1 - index
                });
                if (children.children) {
                    children = children.children[bbqData[index]];
                }
            })

            $exploreNavigation.html(sakai.api.Util.TemplateRenderer(exploreNavigationTemplate,{"breadcrumb": breadcrumb}));
        };

        /**
         * Generate the navigation object and pass it to the left hand navigation widget
         * @param {Object} navData Contains all data from the category the user is currently viewing
         */
        var generateNav = function(navData){

            toplevelId = navData.id;

            pubdata = {
                "structure0": {}
            };
            privdata = {
                "structure0": {}
            };

            var rnd =  Math.floor(Math.random() * 999999999);
            privdata["structure0"][navData.id] = {
                "_id": navData.id,
                "_order": 0,
                "_ref": rnd,
                "_title": navData.title
            };

            // featuredcontent, featured people and featuredworld random numbers
            var fcRnd = Math.floor(Math.random() * 999999999);
            var fpRnd = Math.floor(Math.random() * 999999999);
            var fwRnd = Math.floor(Math.random() * 999999999);
            privdata[rnd] = {
                page: "

"
            }
            privdata[fcRnd] = {
                navData: navData,
                category: navData.id
            };
            privdata[fpRnd] = {
                navData: navData,
                category: navData.id
            };
            privdata[fwRnd] = {
                navData: navData,
                category: navData.id
            };

            var count = 0;
            $.each(navData.children, function(index, item){
                var rnd = Math.floor(Math.random() * 999999999);
                pubdata["structure0"][navData.id + "/" + index] = {
                    "_ref": rnd,
                    "_order": count,
                    "_title": item.title,
                    "main": {
                        "_ref": rnd,
                        "_order": 0,
                        "_title": item.title
                    }
                };

                // featuredcontent, featured people and featuredworld random numbers
                var fcRnd = Math.floor(Math.random() * 999999999);
                var fpRnd = Math.floor(Math.random() * 999999999);
                var fwRnd = Math.floor(Math.random() * 999999999);
                pubdata[rnd] = {
                    page: "

"
                }
                pubdata[fcRnd] = {
                    navData: navData,
                    category: navData.id + "/" + index
                };
                pubdata[fpRnd] = {
                    navData: navData,
                    category: navData.id + "/" + index
                };
                pubdata[fwRnd] = {
                    navData: navData,
                    category: navData.id
                };

                count++;
            });
            debug.log(pubdata);
            $(window).trigger("lhnav.init", [pubdata, privdata, {}]);
        };

        /**
         * Get the category out of the URL and give it back
         * @return {Array} Array of strings representing the selected hierarchy
         */
        var getCategory = function(){
            var category = $.bbq.getState("tag");
            if(!category){
                category = $.bbq.getState("l").split("/");
            }else{
                category = category.split("/");
            }
            return category;
        };

        var doInit = function(){
            var category = getCategory();
            sakai.config.Directory[category[0]].id = category[0];
            generateNav(sakai.config.Directory[category[0]]);
            createBreadcrumb(sakai.config.Directory[category[0]], category);
        };

        $(window).bind("lhnav.ready", function(){
            doInit();
        });

        $(window).bind("hashchange", function(e, data){
            var category = getCategory();
            createBreadcrumb(sakai.config.Directory[category[0]], category);
        });

    };

    sakai.api.Widgets.Container.registerForLoad("category");
});<commit_msg>SAKIII-2929 Fix lhnav<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

require(["jquery","sakai/sakai.api.core"], function($, sakai) {

    sakai_global.category = sakai_global.category || {};

    sakai_global.category = function() {

        var pubdata = {};
        var privdata = {};

        // Containers
        var $exploreNavigation = $("#explore_navigation");
        var toplevelId = "";

        // Templates
        var exploreNavigationTemplate = "explore_navigation_template";

        /**
         * Create the breadcrumb data and render on screen
         * @param {Object} dirData Object that contains children for the category
         * @param {Array} bbqData Array of IDs fetched with bbq to help identify correct children
         */
        var createBreadcrumb = function(dirData, bbqData){
            // Create top level breadcrumb
            var breadcrumb = [];
            breadcrumb.push({
                "title": sakai.api.i18n.General.getValueForKey("ALL_CATEGORIES"),
                "id": bbqData[0],
                "link": true,
                "url": "/dev/allcategories.html"
            });
            breadcrumb.push({
                "title": dirData.title,
                "id": dirData.id,
                "link": bbqData.length - 1
            });
            bbqData.splice(0,1);

            // Create children level breadcrumb
            var children = dirData.children[bbqData[0]];
            $.each(bbqData, function(index, item){
                breadcrumb.push({
                    "title": children.title,
                    "id": item,
                    "link": bbqData.length - 1 - index
                });
                if (children.children) {
                    children = children.children[bbqData[index]];
                }
            })

            $exploreNavigation.html(sakai.api.Util.TemplateRenderer(exploreNavigationTemplate,{"breadcrumb": breadcrumb}));
        };

        /**
         * Generate the navigation object and pass it to the left hand navigation widget
         * @param {Object} navData Contains all data from the category the user is currently viewing
         */
        var generateNav = function(navData){

            toplevelId = navData.id;

            pubdata = {
                "structure0": {}
            };
            privdata = {
                "structure0": {}
            };

            var rnd =  sakai.api.Util.generateWidgetId();
            privdata["structure0"][navData.id] = {
                "_order": 0,
                "_ref": rnd,
                "_title": navData.title
            };

            // featuredcontent, featured people and featuredworld random numbers
            var fcRnd = sakai.api.Util.generateWidgetId();
            var fpRnd = sakai.api.Util.generateWidgetId();
            var fwRnd = sakai.api.Util.generateWidgetId();
            privdata[rnd] = {
                page: "

"
            }
            privdata[fcRnd] = {
                navData: navData,
                category: navData.id
            };
            privdata[fpRnd] = {
                navData: navData,
                category: navData.id
            };
            privdata[fwRnd] = {
                navData: navData,
                category: navData.id
            };

            var count = 0;
            $.each(navData.children, function(index, item){
                var rnd = sakai.api.Util.generateWidgetId();
                pubdata["structure0"][navData.id + "/" + index] = {
                    "_ref": rnd,
                    "_order": count,
                    "_title": item.title,
                    "main": {
                        "_ref": rnd,
                        "_order": 0,
                        "_title": item.title
                    }
                };

                // featuredcontent, featured people and featuredworld random numbers
                var fcRnd = sakai.api.Util.generateWidgetId();
                var fpRnd = sakai.api.Util.generateWidgetId();
                var fwRnd = sakai.api.Util.generateWidgetId();
                pubdata[rnd] = {
                    page: "

"
                }
                pubdata[fcRnd] = {
                    navData: navData,
                    category: navData.id + "/" + index
                };
                pubdata[fpRnd] = {
                    navData: navData,
                    category: navData.id + "/" + index
                };
                pubdata[fwRnd] = {
                    navData: navData,
                    category: navData.id
                };

                count++;
            });
            debug.log(pubdata);
            debug.log(privdata);
            $(window).trigger("lhnav.init", [pubdata, privdata, {}]);
        };

        /**
         * Get the category out of the URL and give it back
         * @return {Array} Array of strings representing the selected hierarchy
         */
        var getCategory = function(){
            var category = $.bbq.getState("tag");
            if(!category){
                category = $.bbq.getState("l").split("/");
            }else{
                category = category.split("/");
            }
            return category;
        };

        var doInit = function(){
            var category = getCategory();
            sakai.config.Directory[category[0]].id = category[0];
            generateNav(sakai.config.Directory[category[0]]);
            createBreadcrumb(sakai.config.Directory[category[0]], category);
        };

        $(window).bind("lhnav.ready", function(){
            doInit();
        });

        $(window).bind("hashchange", function(e, data){
            var category = getCategory();
            createBreadcrumb(sakai.config.Directory[category[0]], category);
        });

    };

    sakai.api.Widgets.Container.registerForLoad("category");
});<|endoftext|>


<commit_before>var fs = require('fs');
var expect = require('chai').expect;
var nock = require('nock');
var validateCss = require('../');

function runValidateCss() {
  before(function (done) {
    var that = this;
    validateCss(this.css, function (err, data) {
      that.err = err;
      that.data = data;
      done();
    });
  });
}
function wait(ms) {
  before(function (done) {
    setTimeout(done, ms);
  });
}

if (process.env.FIXTURE_HTTP) {
  before(function () {
    this._invalidXml = nock('http://jigsaw.w3.org')
                        .post('/css-validator/validator')
                        .reply(200, fs.readFileSync(__dirname + '/test-files/invalid.xml', 'utf8'));
  });
  after(function () {
    this._invalidXml.done();
  });
}

describe('A valid CSS file', function () {
  before(function () {
    this.css = fs.readFileSync(__dirname + '/test-files/valid.css', 'utf8');
  });

  describe('when validated', function () {
    runValidateCss();

    it('has no errors', function () {
      expect(this.data.validity).to.equal(true);
      expect(this.data.errors).to.deep.equal([]);
      expect(this.data.warnings).to.deep.equal([]);
    });
  });
});

describe('A invalid CSS file', function () {
  before(function () {
    this.css = fs.readFileSync(__dirname + '/test-files/invalid.css', 'utf8');
  });

  describe('when validated', function () {
    // Wait a seconds for the w3c rate limiting
    wait(1000);
    runValidateCss();

    it('was not valid errors', function () {
      expect(this.data.validity).to.equal(false);
    });

    it('has an expected error', function () {
      var errors = this.data.errors;
      expect(errors.length).to.equal(1);
      expect(errors[0].message).to.contain('background-color');
    });

    it('has an expected warning', function () {
      var warnings = this.data.warnings;
      expect(warnings.length).to.equal(1);
      expect(warnings[0].message).to.contain('-moz-box-sizing');
    });
  });
});
<commit_msg>Revert "Adding delay to tests"<commit_after>var fs = require('fs');
var expect = require('chai').expect;
var nock = require('nock');
var validateCss = require('../');

function runValidateCss() {
  before(function (done) {
    var that = this;
    validateCss(this.css, function (err, data) {
      that.err = err;
      that.data = data;
      done();
    });
  });
}

if (process.env.FIXTURE_HTTP) {
  before(function () {
    this._invalidXml = nock('http://jigsaw.w3.org')
                        .post('/css-validator/validator')
                        .reply(200, fs.readFileSync(__dirname + '/test-files/invalid.xml', 'utf8'));
  });
  after(function () {
    this._invalidXml.done();
  });
}

describe('A valid CSS file', function () {
  before(function () {
    this.css = fs.readFileSync(__dirname + '/test-files/valid.css', 'utf8');
  });

  describe('when validated', function () {
    runValidateCss();

    it('has no errors', function () {
      expect(this.data.validity).to.equal(true);
      expect(this.data.errors).to.deep.equal([]);
      expect(this.data.warnings).to.deep.equal([]);
    });
  });
});

describe('A invalid CSS file', function () {
  before(function () {
    this.css = fs.readFileSync(__dirname + '/test-files/invalid.css', 'utf8');
  });

  describe('when validated', function () {
    runValidateCss();

    it('was not valid errors', function () {
      expect(this.data.validity).to.equal(false);
    });

    it('has an expected error', function () {
      var errors = this.data.errors;
      expect(errors.length).to.equal(1);
      expect(errors[0].message).to.contain('background-color');
    });

    it('has an expected warning', function () {
      var warnings = this.data.warnings;
      expect(warnings.length).to.equal(1);
      expect(warnings[0].message).to.contain('-moz-box-sizing');
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.controller:ChatCtrl
 * @description
 * # Chat Ctrl
 * Show Pad for a given project
 */

angular.module('Pear2Pear')
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/communities/:comId/projects/:id/pad', {
        templateUrl: 'pad/show.html',
        controller: 'PadCtrl'
      });
  }])
  .controller('PadCtrl', [
              'SwellRTSession', 'url', '$rootScope', '$scope', '$route', '$location', '$timeout', 'SharedState', 'ProjectsSvc', '$rootElement',
              function(SwellRTSession, url, $rootScope, $scope, $route, $location, $timeout, SharedState, ProjectsSvc, $rootElement){

    $scope.urlId = url.urlId;
    $scope.communityId = $route.current.params.comId;

    SwellRTSession.onLoad(function(){
      ProjectsSvc.find($route.current.params.id)
        .then(function(proxy) {
          $scope.project = proxy;
          $scope.project.timestampProjectAccess();
        });
    });

    // Should use activeLinks, but https://github.com/mcasimir/mobile-angular-ui/issues/262
    $scope.nav = function(id) {
      return id === 'pad' ? 'active' : '';
    };

    $scope.titleReminder = function titleReminder() {
      SharedState.turnOff('projectTitleReminder');

      document.querySelector('.project-title input').focus();
    };

    $scope.cancelProject = function() {
      SharedState.turnOff('projectTitleReminder');
      $scope.project.type = 'deleted';
      $scope.project.communities = [];
      $location.path('frontpage');
    };

    angular.element(document.querySelector('.swellrt-editor')).on(
      'focusin',
      function(){
        $timeout(function(){
          document.getElementById('pad').focus();
          $scope.toggleFullScreenEdit();
        });
      });

    // Do not leave pad without giving a title to the project
    $rootScope.$on('$routeChangeStart', function(event) {
      if ($scope.project.type !== 'deleted' && ($scope.project.title === undefined || $scope.project.title === '')) {
        event.preventDefault();

        SharedState.turnOn('projectTitleReminder');
      }
    });

    $scope.ed = {
      editting: false
    };

    $scope.toggleFullScreenEdit = function() {

      $scope.ed.editting = !$scope.ed.editting;

      if ($scope.ed.editting) {
        $rootElement.removeClass('has-navbar-top');
        document.getElementById('pad').focus();
      } else {
        $rootElement.addClass('has-navbar-top');
      }

      $rootScope.hideNavigation = $scope.ed.editting;
    };

  }]);
<commit_msg>do not toggle to full screen edition if user is not contributor<commit_after>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.controller:ChatCtrl
 * @description
 * # Chat Ctrl
 * Show Pad for a given project
 */

angular.module('Pear2Pear')
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/communities/:comId/projects/:id/pad', {
        templateUrl: 'pad/show.html',
        controller: 'PadCtrl'
      });
  }])
  .controller('PadCtrl', [
              'SwellRTSession', 'url', '$rootScope', '$scope', '$route', '$location', '$timeout', 'SharedState', 'ProjectsSvc', '$rootElement',
              function(SwellRTSession, url, $rootScope, $scope, $route, $location, $timeout, SharedState, ProjectsSvc, $rootElement){

    $scope.urlId = url.urlId;
    $scope.communityId = $route.current.params.comId;

    SwellRTSession.onLoad(function(){
      ProjectsSvc.find($route.current.params.id)
        .then(function(proxy) {
          $scope.project = proxy;
          $scope.project.timestampProjectAccess();
        });
    });

    // Should use activeLinks, but https://github.com/mcasimir/mobile-angular-ui/issues/262
    $scope.nav = function(id) {
      return id === 'pad' ? 'active' : '';
    };

    $scope.titleReminder = function titleReminder() {
      SharedState.turnOff('projectTitleReminder');

      document.querySelector('.project-title input').focus();
    };

    $scope.cancelProject = function() {
      SharedState.turnOff('projectTitleReminder');
      $scope.project.type = 'deleted';
      $scope.project.communities = [];
      $location.path('frontpage');
    };

    angular.element(document.querySelector('.swellrt-editor')).on(
      'focusin',
      function(){
        if ($scope.project.isContributor()){
          $timeout(function(){
            document.getElementById('pad').focus();
            $scope.toggleFullScreenEdit();
          });
        }
      });

    // Do not leave pad without giving a title to the project
    $rootScope.$on('$routeChangeStart', function(event) {
      if ($scope.project.type !== 'deleted' && ($scope.project.title === undefined || $scope.project.title === '')) {
        event.preventDefault();

        SharedState.turnOn('projectTitleReminder');
      }
    });

    $scope.ed = {
      editting: false
    };

    $scope.toggleFullScreenEdit = function() {

      $scope.ed.editting = !$scope.ed.editting;

      if ($scope.ed.editting) {
        $rootElement.removeClass('has-navbar-top');
        document.getElementById('pad').focus();
      } else {
        $rootElement.addClass('has-navbar-top');
      }

      $rootScope.hideNavigation = $scope.ed.editting;
    };

  }]);
<|endoftext|>


<commit_before>import {
  render,
  renderString,
  stop as stopTwigRenderer,
} from '@bolt/twig-renderer';
import { fixture as html } from '@open-wc/testing-helpers';

const { readYamlFileSync } = require('@bolt/build-tools/utils/yaml');
const { join } = require('path');
const schema = readYamlFileSync(join(__dirname, '../button.schema.yml'));
const { tag } = schema.properties;

async function renderTwig(template, data) {
  return await render(template, data, true);
}

async function renderTwigString(template, data) {
  return await renderString(template, data, true);
}

const timeout = 60000;

describe('button', async () => {
  let page;

  afterAll(async () => {
    await stopTwigRenderer();
  }, timeout);

  beforeEach(async () => {
    page = await global.__BROWSER__.newPage();
    await page.goto('http://127.0.0.1:4444/', {
      timeout: 0,
      waitLoad: true,
      waitNetworkIdle: true, // defaults to false
    });
  }, timeout);

  test('basic button', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Hello World',
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  tag.enum.forEach(async tagChoice => {
    test(`button tag: ${tagChoice}`, async () => {
      const results = await renderTwig('@bolt-components-button/button.twig', {
        text: 'Hello World',
        tag: tagChoice,
      });
      expect(results.ok).toBe(true);
      expect(results.html).toMatchSnapshot();
    });
  });

  test('Button with outer classes via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer classes',
      attributes: {
        class: ['u-bolt-padding-medium'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with inner classes via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with inner classes',
      attributes: {
        class: ['is-active'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with outer JS-class via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer JS-prefixed class',
      attributes: {
        class: ['js-click-me'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with c-bolt- class is thrown out', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer JS-prefixed class',
      attributes: {
        class: ['c-bolt-button--secondary'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with an onClick param renders properly', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with onClick via param',
      onClick: 'on-click-test',
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with an onClick attributes renders properly', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button w/ onClick via attributes',
      attributes: {
        'on-click': 'on-click-test',
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Default <bolt-button> w/o Shadow DOM renders', async function() {
    const renderedButtonHTML = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Hello World!';
      document.body.appendChild(btn);
      btn.useShadow = false;
      btn.updated();
      return btn.outerHTML;
    });

    const renderedHTML = await html(renderedButtonHTML);

    expect(
      renderedHTML
        .querySelector('.c-bolt-button')
        .classList.contains('c-bolt-button--primary'),
    ).toBe(true);

    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      failureThreshold: '0.01',
      failureThresholdType: 'percent',
    });

    expect(renderedHTML).toMatchSnapshot();
  });

  test('Default <bolt-button> with Shadow DOM renders', async function() {
    const defaultButtonShadowRoot = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Button Test -- Shadow Root HTML';
      document.body.appendChild(btn);
      btn.updated();
      return btn.renderRoot.innerHTML;
    });

    const defaultButtonOuter = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Button Test -- Outer HTML';
      document.body.appendChild(btn);
      btn.updated();
      return btn.outerHTML;
    });

    const renderedShadowDomHTML = await html(defaultButtonShadowRoot);
    const renderedHTML = await html(defaultButtonOuter);

    expect(renderedHTML.textContent).toEqual('Button Test -- Outer HTML');
    // expect(
    //   renderedShadowDomHTML
    //     .querySelector('.c-bolt-button')
    //     .classList.contains('c-bolt-button--primary'),
    // ).toBe(true);

    // expect(renderedShadowDomHTML.querySelector('style')).toBe(true);
    // expect(renderedShadowDomHTML.querySelector('button')).toBe(true);

    const image = await page.screenshot();

    expect(image).toMatchImageSnapshot({
      failureThreshold: '0.01',
      failureThresholdType: 'percent',
    });

    expect(renderedShadowDomHTML).toMatchSnapshot();
    expect(renderedHTML).toMatchSnapshot();
  });
});
<commit_msg>refactor: increase the precision of the button component's test config when running vrt<commit_after>import {
  render,
  renderString,
  stop as stopTwigRenderer,
} from '@bolt/twig-renderer';
import { fixture as html } from '@open-wc/testing-helpers';

const { readYamlFileSync } = require('@bolt/build-tools/utils/yaml');
const { join } = require('path');
const schema = readYamlFileSync(join(__dirname, '../button.schema.yml'));
const { tag } = schema.properties;

async function renderTwig(template, data) {
  return await render(template, data, true);
}

async function renderTwigString(template, data) {
  return await renderString(template, data, true);
}

const timeout = 60000;

describe('button', async () => {
  let page;

  afterAll(async () => {
    await stopTwigRenderer();
  }, timeout);

  beforeEach(async () => {
    page = await global.__BROWSER__.newPage();
    await page.goto('http://127.0.0.1:4444/', {
      timeout: 0,
      waitLoad: true,
      waitNetworkIdle: true, // defaults to false
    });
  }, timeout);

  test('basic button', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Hello World',
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  tag.enum.forEach(async tagChoice => {
    test(`button tag: ${tagChoice}`, async () => {
      const results = await renderTwig('@bolt-components-button/button.twig', {
        text: 'Hello World',
        tag: tagChoice,
      });
      expect(results.ok).toBe(true);
      expect(results.html).toMatchSnapshot();
    });
  });

  test('Button with outer classes via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer classes',
      attributes: {
        class: ['u-bolt-padding-medium'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with inner classes via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with inner classes',
      attributes: {
        class: ['is-active'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with outer JS-class via Drupal Attributes', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer JS-prefixed class',
      attributes: {
        class: ['js-click-me'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with c-bolt- class is thrown out', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with outer JS-prefixed class',
      attributes: {
        class: ['c-bolt-button--secondary'],
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with an onClick param renders properly', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button with onClick via param',
      onClick: 'on-click-test',
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Button with an onClick attributes renders properly', async () => {
    const results = await renderTwig('@bolt-components-button/button.twig', {
      text: 'Button w/ onClick via attributes',
      attributes: {
        'on-click': 'on-click-test',
      },
    });
    expect(results.ok).toBe(true);
    expect(results.html).toMatchSnapshot();
  });

  test('Default <bolt-button> w/o Shadow DOM renders', async function() {
    const renderedButtonHTML = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Hello World!';
      document.body.appendChild(btn);
      btn.useShadow = false;
      btn.updated();
      return btn.outerHTML;
    });

    const renderedHTML = await html(renderedButtonHTML);

    expect(
      renderedHTML
        .querySelector('.c-bolt-button')
        .classList.contains('c-bolt-button--primary'),
    ).toBe(true);

    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      failureThreshold: '0',
      failureThresholdType: 'percent',
    });

    expect(renderedHTML).toMatchSnapshot();
  });

  test('Default <bolt-button> with Shadow DOM renders', async function() {
    const defaultButtonShadowRoot = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Button Test -- Shadow Root HTML';
      document.body.appendChild(btn);
      btn.updated();
      return btn.renderRoot.innerHTML;
    });

    const defaultButtonOuter = await page.evaluate(() => {
      const btn = document.createElement('bolt-button');
      btn.textContent = 'Button Test -- Outer HTML';
      document.body.appendChild(btn);
      btn.updated();
      return btn.outerHTML;
    });

    const renderedShadowDomHTML = await html(defaultButtonShadowRoot);
    const renderedHTML = await html(defaultButtonOuter);

    expect(renderedHTML.textContent).toEqual('Button Test -- Outer HTML');
    // expect(
    //   renderedShadowDomHTML
    //     .querySelector('.c-bolt-button')
    //     .classList.contains('c-bolt-button--primary'),
    // ).toBe(true);

    // expect(renderedShadowDomHTML.querySelector('style')).toBe(true);
    // expect(renderedShadowDomHTML.querySelector('button')).toBe(true);

    const image = await page.screenshot();

    expect(image).toMatchImageSnapshot({
      failureThreshold: '0',
      failureThresholdType: 'percent',
    });

    expect(renderedShadowDomHTML).toMatchSnapshot();
    expect(renderedHTML).toMatchSnapshot();
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import { FormattedMessage } from 'react-intl';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';

class ShortTextRespondTask extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      taskAnswerDisabled: true,
    };
  }

  handleSubmit() {
    if (!this.state.taskAnswerDisabled) {
      const response = this.state.response ? this.state.response.trim() : this.props.response;

      this.props.onSubmit(response, this.state.note);
      this.setState({ taskAnswerDisabled: true });
    }
  }

  canSubmit() {
    const taskAnswerDisabled = !(this.state.response && this.state.response.trim());
    this.setState({ taskAnswerDisabled });
  }

  handleChange(e) {
    const state = {};
    state[e.target.name] = e.target.value;
    this.setState(state, this.canSubmit);
  }

  handleCancel() {
    this.setState(
      { response: null, note: '', focus: false },
      this.canSubmit,
    );
    if (this.props.onDismiss) {
      this.props.onDismiss();
    }
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (!this.state.taskAnswerDisabled) {
        this.setState({ taskAnswerDisabled: true });
        this.handleSubmit();
      }
      e.preventDefault();
    }
  }

  render() {
    const actionBtns = (

        <FlatButton
          className="task__cancel"
          label={<FormattedMessage id="shortTextRespondTask.cancelTask" defaultMessage="Cancel" />}
          onClick={this.handleCancel.bind(this)}
        />
        <FlatButton
          className="task__save"
          label={<FormattedMessage id="shortTextRespondTask.resolveTask" defaultMessage="Resolve task" />}
          onClick={this.handleSubmit.bind(this)}
          disabled={this.state.taskAnswerDisabled}
          primary
        />

    );

    const response = typeof this.state.response !== 'undefined' && this.state.response !== null
      ? this.state.response : this.props.response || '';

    const note = typeof this.state.note !== 'undefined' && this.state.note !== null
      ? this.state.note : this.props.note || '';

    return (

        <TextField
          hintText={
            <FormattedMessage
              id="shortTextRespondTask.responseHint"
              defaultMessage="Answer here."
            />
          }
          className="task__response-input"
          value={response}
          name="response"
          onChange={this.handleChange.bind(this)}
          onKeyPress={this.handleKeyPress.bind(this)}
          onFocus={() => { this.setState({ focus: true }); }}
          fullWidth
          multiLine
        />
        <TextField
          hintText={
            <FormattedMessage
              id="shortTextRespondTask.noteHint"
              defaultMessage="Note any additional details here."
            />
          }
          value={note}
          name="note"
          onChange={this.handleChange.bind(this)}
          onFocus={() => { this.setState({ focus: true }); }}
          fullWidth
          multiLine
        />
        {this.state.focus || this.props.response ? actionBtns : null}

    );
  }
}

export default ShortTextRespondTask;
<commit_msg>#6808 Fix short text task response validation<commit_after>import React from 'react';
import { FormattedMessage } from 'react-intl';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';

class ShortTextRespondTask extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      taskAnswerDisabled: true,
    };
  }

  handleSubmit() {
    if (!this.state.taskAnswerDisabled) {
      const response = this.state.response ? this.state.response.trim() : this.props.response;

      this.props.onSubmit(response, this.state.note);
      this.setState({ taskAnswerDisabled: true });
    }
  }

  canSubmit() {
    const response = this.state.response
      ? this.state.response.trim()
      : typeof this.state.response === 'undefined' && this.props.response;

    this.setState({ taskAnswerDisabled: !response });
  }

  handleChange(e) {
    const state = {};
    state[e.target.name] = e.target.value;
    this.setState(state, this.canSubmit);
  }

  handleCancel() {
    this.setState(
      { response: null, note: '', focus: false },
      this.canSubmit,
    );
    if (this.props.onDismiss) {
      this.props.onDismiss();
    }
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (!this.state.taskAnswerDisabled) {
        this.setState({ taskAnswerDisabled: true });
        this.handleSubmit();
      }
      e.preventDefault();
    }
  }

  render() {
    const actionBtns = (

        <FlatButton
          className="task__cancel"
          label={<FormattedMessage id="shortTextRespondTask.cancelTask" defaultMessage="Cancel" />}
          onClick={this.handleCancel.bind(this)}
        />
        <FlatButton
          className="task__save"
          label={<FormattedMessage id="shortTextRespondTask.resolveTask" defaultMessage="Resolve task" />}
          onClick={this.handleSubmit.bind(this)}
          disabled={this.state.taskAnswerDisabled}
          primary
        />

    );

    const response = typeof this.state.response !== 'undefined' && this.state.response !== null
      ? this.state.response : this.props.response || '';

    const note = typeof this.state.note !== 'undefined' && this.state.note !== null
      ? this.state.note : this.props.note || '';

    return (

        <TextField
          hintText={
            <FormattedMessage
              id="shortTextRespondTask.responseHint"
              defaultMessage="Answer here."
            />
          }
          className="task__response-input"
          value={response}
          name="response"
          onChange={this.handleChange.bind(this)}
          onKeyPress={this.handleKeyPress.bind(this)}
          onFocus={() => { this.setState({ focus: true }); }}
          fullWidth
          multiLine
        />
        <TextField
          hintText={
            <FormattedMessage
              id="shortTextRespondTask.noteHint"
              defaultMessage="Note any additional details here."
            />
          }
          value={note}
          name="note"
          onChange={this.handleChange.bind(this)}
          onFocus={() => { this.setState({ focus: true }); }}
          fullWidth
          multiLine
        />
        {this.state.focus || this.props.response ? actionBtns : null}

    );
  }
}

export default ShortTextRespondTask;
<|endoftext|>