<commit_before>'use strict';

const sysPath = require('path');
const helpers = require('./helpers');
const shims = require('./shims');
const deepExtend = require('../helpers').deepExtend;
const mediator = require('../mediator');

const not = helpers.not;

const getModuleRootPath = path => {
  const split = path.split(sysPath.sep);
  const index = split.lastIndexOf('node_modules');
  const scoped = split[index + 1][0] === '@';
  return split.slice(0, index + 2 + (scoped ? 1 : 0)).join(sysPath.sep);
};

let rootMainCache = {};
const getMainCached = path => rootMainCache[getModuleRootPath(path)];
const cacheMain = path => _getMainFile(path).then(file => rootMainCache[getModuleRootPath(path)] = file);

const resetCache = () => rootMainCache = {};

const getModuleRootName = path => {
  const split = path.split(sysPath.sep);
  const index = split.lastIndexOf('node_modules');
  const scoped = split[index + 1][0] === '@';
  return split.slice(index + 1, index + 2 + (scoped ? 1 : 0)).join('/');
};

const getModuleFullRootName = path => {
  const split = path.split(sysPath.sep);
  const indexS = split.indexOf('node_modules');
  const indexE = split.lastIndexOf('node_modules');
  const scoped = split[indexE + 1][0] === '@';
  return split.slice(indexS + 1, indexE + 2 + (scoped ? 1 : 0)).join('/');
};

const definition = (name, exp) => {
  return `require.register("${name}", function(exports, require, module) {
  module.exports = ${exp};
});`;
};

const aliasDef = (target, source) => {
  return definition(target, `require("${source}")`);
};

const simpleShimDef = (name, obj) => {
  return definition(name, JSON.stringify(obj));
};

const relativeToRoot = (filePath, relPath) => sysPath.join(
  getModuleRootPath(filePath), relPath
);

const applyPackageOverrides = pkg => {
  const pkgOverride = mediator.overrides[pkg.name];

  if (pkgOverride) {
    pkg = deepExtend(pkg, pkgOverride);
  }

  return pkg;
};
const getDepPackageJson = depPath => {
  const depJson = require(sysPath.join(depPath, 'package.json'));
  applyPackageOverrides(depJson);
  return depJson;
};

const packageJson = filePath => getDepPackageJson(
  getModuleRootPath(filePath)
);

const browserMappings = filePath => {
  const pkg = packageJson(filePath);
  const browser = pkg.browser || pkg.browserify;
  if (browser && typeof browser === 'object') {
    return browser;
  } else if (browser) {
    const obj = {};
    const path = sysPath.relative(getModuleRootPath(filePath), getMainCached(filePath));
    obj['./' + path] = './' + sysPath.join('.', browser);
    return obj;
  } else {
    return {};
  }
};

const _getMainFile = filePath => {
  const root = getModuleRootPath(filePath);
  const json = packageJson(filePath);

  return _mainFile(root, json);
};

const _mainFile = (root, json) => {
  const depMain = json.main || 'index.js';
  const fileOrDir = sysPath.join(root, depMain);
  return helpers.isDir(fileOrDir).then(isDir => {
    if (isDir) {
      return sysPath.join(fileOrDir, 'index.js');
    } else {
      return fileOrDir.indexOf('.js') === -1 ? fileOrDir + '.js' : fileOrDir;
    }
  });
};

const globalBrowserMappings = filePath => {
  const brMap = browserMappings(filePath);

  return Object.keys(brMap).filter(not(helpers.isRelative)).reduce((newBrMap, key) => {
    const val = brMap[key];
    if (val) {
      newBrMap[key] = helpers.isRelative(val) ? generateModuleName(relativeToRoot(filePath, val)) : val;
    }
    return newBrMap;
  }, {});
};

const expandedFilePath = filePath => {
  const brMap = browserMappings(filePath);

  Object.keys(brMap).filter(helpers.isRelative).forEach(key => {
    const val = brMap[key];
    if (val && filePath === relativeToRoot(filePath, val)) {
      filePath = relativeToRoot(filePath, key);
    }
  });

  return filePath;
};

const isMain = filePath => getMainCached(filePath) === sysPath.resolve(expandedFilePath(filePath));

const getNewHeader = (moduleName, source, filePath, origPath) => {
  const brMap = globalBrowserMappings(filePath);

  const p = filePath.replace(getModuleRootPath(filePath), '').replace('.json', '').replace('.js', '').split(sysPath.sep).slice(0, -1);
  const p2 = [moduleName].concat(p).join('/');
  const itemPath = isMain(filePath) ? `, '${p2}/'` : '';

  const glob = shims.findGlobals(source);

  if (shims.shouldOverrideModuleName(moduleName)) moduleName = shims.overrideModuleName(moduleName);

  if (filePath.indexOf('.json') === -1) {
    const fbModuleName = generateFileBasedModuleName(filePath);
    const fbAlias = mediator.npm.noFileBased ? shims.shouldIncludeFileBasedAlias(moduleName) : isMain(filePath) ?
      aliasDef(fbModuleName, moduleName) + '\n' :
      '';
    const fbModuleNameUnexp = generateFileBasedModuleName(origPath);
    const fbAliasUnexp = mediator.npm.noFileBased ?
      '' :
      fbModuleNameUnexp !== fbModuleName ?
        aliasDef(fbModuleNameUnexp, moduleName) :
        '';

    const mappings = JSON.stringify(brMap);
    const name = getModuleFullRootName(filePath);
    const aliases = (fbAlias + fbAliasUnexp).trim();
    const aliasesFull = aliases ? '\n\n' + aliases : '';
    return (
`\nrequire.register("${moduleName}", function(exports, require, module) {
  require = __makeRelativeRequire(require, ${mappings}, "${name}"${itemPath});
  ${glob}(function() {
    ${source.trim()}
  })();
});${aliasesFull}`);
  } else {
    return definition(moduleName, source);
  }
};

const generateModule = (filePath, source) => {
  const expandedPath = expandedFilePath(filePath);
  const mn = generateModuleName(expandedPath);
  return getNewHeader(mn, source, expandedPath, filePath);
};

const slashes = string => string.replace(/\\/g, '/');

const generateModuleName = filePath => {
  const rp = getModuleRootPath(filePath);
  const mn = getModuleFullRootName(filePath) +
    (isMain(filePath) ? '' : filePath.replace(rp, '').replace('.json', '').replace('.js', ''));

  return slashes(mn);
};

const generateFileBasedModuleName = filePath => {
  return slashes(getModuleFullRootName(filePath) + filePath.replace(getModuleRootPath(filePath), '').replace('.json', '').replace('.js', ''));
};

const makeRequire = (
`\nvar __makeRelativeRequire = function(require, mappings, pref, fullPath) {
  if (fullPath) {
    var req = require;
    require = function(path) {
      return req(path.replace('./', fullPath));
    };
  }
  var none = {};
  var tryReq = function(name, pref) {
    var val;
    try {
      val = require(pref + '/node_modules/' + name);
      return val;
    } catch (e) {
      if (e.toString().indexOf('Cannot find module') === -1) {
        throw e;
      }

      if (pref.indexOf('node_modules') !== -1) {
        var s = pref.split('/');
        var i = s.lastIndexOf('node_modules');
        var newPref = s.slice(0, i).join('/');
        return tryReq(name, newPref);
      }
    }
    return none;
  };
  return function(name) {
    if (mappings[name] !== undefined) name = mappings[name];
    name = name.replace(/\.(json|js)$/, '');
    if (name[0] !== '.' && pref) {
      var val = tryReq(name, pref);
      if (val !== none) return val;
    }
    return require(name);
  }
};
`);

module.exports = {aliasDef, simpleShimDef, applyPackageOverrides, generateModule, generateModuleName, getModuleRootName, makeRequire, cacheMain, packageJson, resetCache};
<commit_msg>Properly resolve redefined main file without a dot (like rx)<commit_after>'use strict';

const sysPath = require('path');
const helpers = require('./helpers');
const shims = require('./shims');
const deepExtend = require('../helpers').deepExtend;
const mediator = require('../mediator');

const not = helpers.not;

const getModuleRootPath = path => {
  const split = path.split(sysPath.sep);
  const index = split.lastIndexOf('node_modules');
  const scoped = split[index + 1][0] === '@';
  return split.slice(0, index + 2 + (scoped ? 1 : 0)).join(sysPath.sep);
};

let rootMainCache = {};
const getMainCached = path => rootMainCache[getModuleRootPath(path)];
const cacheMain = path => _getMainFile(path).then(file => rootMainCache[getModuleRootPath(path)] = file);

const resetCache = () => rootMainCache = {};

const getModuleRootName = path => {
  const split = path.split(sysPath.sep);
  const index = split.lastIndexOf('node_modules');
  const scoped = split[index + 1][0] === '@';
  return split.slice(index + 1, index + 2 + (scoped ? 1 : 0)).join('/');
};

const getModuleFullRootName = path => {
  const split = path.split(sysPath.sep);
  const indexS = split.indexOf('node_modules');
  const indexE = split.lastIndexOf('node_modules');
  const scoped = split[indexE + 1][0] === '@';
  return split.slice(indexS + 1, indexE + 2 + (scoped ? 1 : 0)).join('/');
};

const definition = (name, exp) => {
  return `require.register("${name}", function(exports, require, module) {
  module.exports = ${exp};
});`;
};

const aliasDef = (target, source) => {
  return definition(target, `require("${source}")`);
};

const simpleShimDef = (name, obj) => {
  return definition(name, JSON.stringify(obj));
};

const relativeToRoot = (filePath, relPath) => sysPath.join(
  getModuleRootPath(filePath), relPath
);

const applyPackageOverrides = pkg => {
  const pkgOverride = mediator.overrides[pkg.name];

  if (pkgOverride) {
    pkg = deepExtend(pkg, pkgOverride);
  }

  return pkg;
};
const getDepPackageJson = depPath => {
  const depJson = require(sysPath.join(depPath, 'package.json'));
  applyPackageOverrides(depJson);
  return depJson;
};

const packageJson = filePath => getDepPackageJson(
  getModuleRootPath(filePath)
);

const browserMappings = filePath => {
  const pkg = packageJson(filePath);
  const browser = pkg.browser || pkg.browserify;
  if (browser && typeof browser === 'object') {
    return browser;
  } else if (browser) {
    const obj = {};
    if (!getMainCached(filePath)) return {};
    const path = sysPath.relative(getModuleRootPath(filePath), getMainCached(filePath));
    obj['./' + path] = './' + sysPath.join('.', browser);
    return obj;
  } else {
    return {};
  }
};

const _getMainFile = filePath => {
  const root = getModuleRootPath(filePath);
  const json = packageJson(filePath);

  return _mainFile(root, json);
};

const globalBrowserMappings = filePath => {
  const brMap = browserMappings(filePath);

  return Object.keys(brMap).filter(not(helpers.isRelative)).reduce((newBrMap, key) => {
    const val = brMap[key];
    newBrMap[key] = val ? (helpers.isRelative(val) ? generateModuleName(relativeToRoot(filePath, val)) : val) : false;
    return newBrMap;
  }, {});
};

const unexpandedFilePath = filePath => {
  const brMap = browserMappings(filePath);

  Object.keys(brMap).forEach(key => {
    const val = brMap[key];
    if (val && key && filePath === relativeToRoot(filePath, key)) {
      filePath = relativeToRoot(filePath, val);
    }
  });

  return filePath;
};

const expandedFilePath = filePath => {
  const brMap = browserMappings(filePath);

  Object.keys(brMap).filter(helpers.isRelative).forEach(key => {
    const val = brMap[key];
    if (val && filePath === relativeToRoot(filePath, val)) {
      filePath = relativeToRoot(filePath, key);
    }
  });

  return filePath;
};

const _mainFile = (root, json) => {
  const depMain = json.main || 'index.js';
  const fileOrDir = sysPath.join(root, depMain);
  return helpers.isDir(fileOrDir).then(isDir => {
    if (isDir) {
      return sysPath.join(fileOrDir, 'index.js');
    } else {
      return fileOrDir.indexOf('.js') === -1 ? fileOrDir + '.js' : fileOrDir;
    }
  }).then(main => {
    return unexpandedFilePath(main);
  });
};

const isMain = filePath => getMainCached(filePath) === sysPath.resolve(expandedFilePath(filePath));

const getNewHeader = (moduleName, source, filePath, origPath) => {
  const brMap = globalBrowserMappings(filePath);

  const p = filePath.replace(getModuleRootPath(filePath), '').replace('.json', '').replace('.js', '').split(sysPath.sep).slice(0, -1);
  const p2 = [moduleName].concat(p).join('/');
  const itemPath = isMain(filePath) ? `, '${p2}/'` : '';

  const glob = shims.findGlobals(source);

  if (shims.shouldOverrideModuleName(moduleName)) moduleName = shims.overrideModuleName(moduleName);

  if (filePath.indexOf('.json') === -1) {
    const fbModuleName = generateFileBasedModuleName(filePath);
    const fbAlias = mediator.npm.noFileBased ? shims.shouldIncludeFileBasedAlias(moduleName) : isMain(filePath) ?
      aliasDef(fbModuleName, moduleName) + '\n' :
      '';
    const fbModuleNameUnexp = generateFileBasedModuleName(origPath);
    const fbAliasUnexp = mediator.npm.noFileBased ?
      '' :
      fbModuleNameUnexp !== fbModuleName ?
        aliasDef(fbModuleNameUnexp, moduleName) :
        '';

    const mappings = JSON.stringify(brMap);
    const name = getModuleFullRootName(filePath);
    const aliases = (fbAlias + fbAliasUnexp).trim();
    const aliasesFull = aliases ? '\n\n' + aliases : '';
    return (
`\nrequire.register("${moduleName}", function(exports, require, module) {
  require = __makeRelativeRequire(require, ${mappings}, "${name}"${itemPath});
  ${glob}(function() {
    ${source.trim()}
  })();
});${aliasesFull}`);
  } else {
    return definition(moduleName, source);
  }
};

const generateModule = (filePath, source) => {
  const expandedPath = expandedFilePath(filePath);
  const mn = generateModuleName(expandedPath);
  return getNewHeader(mn, source, expandedPath, filePath);
};

const slashes = string => string.replace(/\\/g, '/');

const generateModuleName = filePath => {
  const rp = getModuleRootPath(filePath);
  const mn = getModuleFullRootName(filePath) +
    (isMain(filePath) ? '' : filePath.replace(rp, '').replace('.json', '').replace('.js', ''));

  return slashes(mn);
};

const generateFileBasedModuleName = filePath => {
  return slashes(getModuleFullRootName(filePath) + filePath.replace(getModuleRootPath(filePath), '').replace('.json', '').replace('.js', ''));
};

const makeRequire = (
`\nvar __makeRelativeRequire = function(require, mappings, pref, fullPath) {
  if (fullPath) {
    var req = require;
    require = function(path) {
      return req(path.replace('./', fullPath));
    };
  }
  var none = {};
  var tryReq = function(name, pref) {
    var val;
    try {
      val = require(pref + '/node_modules/' + name);
      return val;
    } catch (e) {
      if (e.toString().indexOf('Cannot find module') === -1) {
        throw e;
      }

      if (pref.indexOf('node_modules') !== -1) {
        var s = pref.split('/');
        var i = s.lastIndexOf('node_modules');
        var newPref = s.slice(0, i).join('/');
        return tryReq(name, newPref);
      }
    }
    return none;
  };
  return function(name) {
    if (mappings[name] !== undefined) name = mappings[name];
    name = name.replace(/\.(json|js)$/, '');
    if (name[0] !== '.' && pref) {
      var val = tryReq(name, pref);
      if (val !== none) return val;
    }
    return require(name);
  }
};
`);

module.exports = {aliasDef, simpleShimDef, applyPackageOverrides, generateModule, generateModuleName, getModuleRootName, makeRequire, cacheMain, packageJson, resetCache};
<|endoftext|>


<commit_before>var Server = require('../lib/server');
var Config = require('../lib/config');
var path = require('path');
var request = require('request');
var cheerio = require('cheerio');
var fs = require('fs');
var expect = require('chai').expect;
var http = require('http');
var https = require('https');

describe('Server', function() {
  var baseUrl, server, config;
  var port = 63571;

  describe('http', function() {
    before(function(done) {
      config = new Config('dev', {
        port: port,
        src_files: [
          'web/hello.js',
          {src:'web/hello_tst.js', attrs: ['data-foo="true"', 'data-bar']}
        ],
        routes: {
          '/direct-test': 'web/direct',
          '/fallback-test': ['web/direct', 'web/fallback']
        },
        cwd: 'tests',
        proxies: {
          '/api1': {
            target: 'http://localhost:13372'
          },
          '/api2': {
            target: 'https://localhost:13373',
            secure: false
          },
          '/api3': {
            target: 'http://localhost:13374',
            onlyContentTypes: ['json']
          },
          '/api4': {
            target: 'http://localhost:13375'
          }
        }
      });
      baseUrl = 'http://localhost:' + port + '/';

      server = new Server(config);
      server.start();
      server.once('server-start', function() {
        done();
      });
    });
    after(function(done) {
      server.stop(done);
    });

    it('redirects to an id', function(done) {
      request(baseUrl, { followRedirect: false }, function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(302);
        expect(res.headers.location).to.match(/^\/[0-9]+$/);
        done();
      });
    });

    it('serves the homepage after redirect', function(done) {
      request(baseUrl, { followRedirect: true }, function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(200);
        done();
      });
    });

    it('gets scripts for the home page', function(done) {
      request(baseUrl, function(err, req, text) {
        var $ = cheerio.load(text);
        var srcs = $('script').map(function() { return $(this).attr('src'); }).get();
        expect(srcs).to.deep.equal([
          '//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js',
          '/testem.js',
          '//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js',
          'web' + path.sep + 'hello.js',
          'web' + path.sep + 'hello_tst.js'
        ]);
        done();
      });
    });

    it('gets testem.js', function(done) {
      request(baseUrl + '/testem.js', done);
    });

    it('gets src file', function(done) {
      assertUrlReturnsFileContents(baseUrl + 'web/hello.js', 'tests/web/hello.js', done);
    });

    it('gets bundled files', function(done) {
      assertUrlReturnsFileContents(baseUrl + 'testem/connection.html', 'public/testem/connection.html', done);
    });

    it('serves custom test page', function(done) {
      config.set('test_page', 'web/tests.html');
      assertUrlReturnsFileContents(baseUrl, 'tests/web/tests.html', done);
    });

    it('renders custom test page as template', function(done) {
      config.set('test_page', 'web/tests_template.mustache');
      request(baseUrl, function(err, req, text) {
        expect(text).to.equal(
          [
          '<!doctype html>',
          '',
          '',
          '    ',
          '    ',
          '',
          ''
          ].join('\n'));
        done();
      });
    });

    it('gets a file using a POST request', function(done) {
      request.post(baseUrl + 'web/hello.js', function(err, req, text) {
        expect(text).to.equal(fs.readFileSync('tests/web/hello.js').toString());
        done();
      });
    });

    it('lists directories', function(done) {
      request(baseUrl + 'data', function(err, req, text) {
        expect(text).to.match(/blah.txt<\/a>/);
        done();
      });
    });

    it('serves local content with browser ids', function(done) {
      assertUrlReturnsFileContents(baseUrl + '1234' + '/web/hello.js', 'tests/web/hello.js', done);
    });

    it('serves local content with tap id', function(done) {
      assertUrlReturnsFileContents(baseUrl + '-1' + '/web/hello.js', 'tests/web/hello.js', done);
    });

    it('accepts other http methods', function(done) {
      request.del(baseUrl + '-1' + '/web/hello.js', function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(200);
        done();
      });
    });

    describe('route', function() {
      it('routes server paths to local paths', function(done) {
        assertUrlReturnsFileContents(baseUrl + 'direct-test/test.js', 'tests/web/direct/test.js', done);
      });

      it('allows fallback paths', function(done) {
        var expectedCallbacks = 2;
        var cb = function() {
          if (--expectedCallbacks === 0) {
            done();
          }
        };
        assertUrlReturnsFileContents(baseUrl + 'fallback-test/test.js', 'tests/web/direct/test.js', cb);
        assertUrlReturnsFileContents(baseUrl + 'fallback-test/test2.js', 'tests/web/fallback/test2.js', cb);
      });
    });

    describe('proxies', function() {
      var api1, api2, api3, api4;

      beforeEach(function(done) {
        api1 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('API');
        });
        var options = {
          key: fs.readFileSync('tests/fixtures/certs/localhost.key'),
          cert: fs.readFileSync('tests/fixtures/certs/localhost.cert')
        };
        api2 = https.createServer(options, function(req, res) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('API - 2');
        });
        api3 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'application/json'});
          res.end(JSON.stringify({API: 3}));
        });

        api4 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'application/json'});
          req.on('data', function(data) {
            res.write(data);
          });
          req.on('end', function() {
            res.end();
          });
        });

        api1.listen(13372, function() {
          api2.listen(13373, function() {
            api3.listen(13374, function() {
              api4.listen(13375, function() {
                done();
              });
            });
          });
        });
      });

      afterEach(function(done) {
        api1.close(function() {
          api2.close(function() {
            api3.close(function() {
              api4.close(function() {
                done();
              });
            });
          });
        });
      });

      it('proxies get request to api1', function(done) {
        request.get(baseUrl + 'api1/hello', function(err, req, text) {
          expect(text).to.equal('API');
          done();
        });
      });

      it('proxies get request to api2', function(done) {
        var options = {
          url: baseUrl + 'api2/hello',
          headers: {
            'Content-Type': 'application/json'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('API - 2');
          done();
        });
      });

      it('proxies post request to api1', function(done) {
        var options = {
          url: baseUrl + 'api1/hello',
          headers: {
            Accept: 'application/json'
          }
        };
        request.post(options, function(err, req, text) {
          expect(text).to.equal('API');
          done();
        });
      });

      it('proxies get request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'application/json'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('{"API":3}');
          done();
        });
      });

      it('proxies post request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'application/json'
          }
        };
        request.post(options, function(err, req, text) {
          expect(text).to.equal('{"API":3}');
          done();
        });
      });

      it('proxies post request to api4', function(done) {
        var options = {
          url: baseUrl + 'api4/test',
          headers: {
            Accept: 'application/json'
          },
          body: '{test: \'some value\'}'
        };
        request.post(options, function(err, req, text) {
          if (err) {
            return done(err);
          }
          expect(text).to.equal('{test: \'some value\'}');
          done();
        });
      });

      it('proxies get html request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('Not found: /api3/test');
          done();
        });
      });
    });
  });

  describe('https', function() {
    before(function(done) {
      config = new Config('dev', {
        port: port,
        key: 'tests/fixtures/certs/localhost.key',
        cert: 'tests/fixtures/certs/localhost.cert',
        src_files: [
          'web/hello.js',
          {src:'web/hello_tst.js', attrs: ['data-foo="true"', 'data-bar']}
        ],
        cwd: 'tests'
      });
      baseUrl = 'https://localhost:' + port + '/';

      server = new Server(config);
      server.once('server-start', function() {
        done();
      });
      server.start();
    });
    after(function(done) {
      server.stop(function() {
        done();
      });
    });

    it('gets the home page', function(done) {
      request({ url: baseUrl, strictSSL: false }, done);
    });
  });

  describe('auto port assignment', function() {
    before(function(done) {
      config = new Config('dev', {
        port: 0,
        cwd: 'tests'
      });
      server = new Server(config);
      server.once('server-start', function() {
        done();
      });
      server.start();
    });
    after(function(done) {
      server.stop(function() {
        done();
      });
    });

    it('updates the config with the actual port', function() {
      expect(config.get('port')).not.to.eq(0);
      expect(config.get('port')).to.eq(server.server.address().port);
    });
  });
});

function assertUrlReturnsFileContents(url, file, done) {
  request(url, function(err, res, text) {
    expect(err).to.be.null();
    expect(res.statusCode).to.eq(200);
    expect(text).to.equal(fs.readFileSync(file).toString());
    done();
  });
}
<commit_msg>Increases server tests timeout<commit_after>var Server = require('../lib/server');
var Config = require('../lib/config');
var path = require('path');
var request = require('request');
var cheerio = require('cheerio');
var fs = require('fs');
var expect = require('chai').expect;
var http = require('http');
var https = require('https');

describe('Server', function() {
  this.timeout(10000);

  var baseUrl, server, config;
  var port = 63571;

  describe('http', function() {
    before(function(done) {
      config = new Config('dev', {
        port: port,
        src_files: [
          'web/hello.js',
          {src:'web/hello_tst.js', attrs: ['data-foo="true"', 'data-bar']}
        ],
        routes: {
          '/direct-test': 'web/direct',
          '/fallback-test': ['web/direct', 'web/fallback']
        },
        cwd: 'tests',
        proxies: {
          '/api1': {
            target: 'http://localhost:13372'
          },
          '/api2': {
            target: 'https://localhost:13373',
            secure: false
          },
          '/api3': {
            target: 'http://localhost:13374',
            onlyContentTypes: ['json']
          },
          '/api4': {
            target: 'http://localhost:13375'
          }
        }
      });
      baseUrl = 'http://localhost:' + port + '/';

      server = new Server(config);
      server.start();
      server.once('server-start', function() {
        done();
      });
    });
    after(function(done) {
      server.stop(done);
    });

    it('redirects to an id', function(done) {
      request(baseUrl, { followRedirect: false }, function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(302);
        expect(res.headers.location).to.match(/^\/[0-9]+$/);
        done();
      });
    });

    it('serves the homepage after redirect', function(done) {
      request(baseUrl, { followRedirect: true }, function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(200);
        done();
      });
    });

    it('gets scripts for the home page', function(done) {
      request(baseUrl, function(err, req, text) {
        var $ = cheerio.load(text);
        var srcs = $('script').map(function() { return $(this).attr('src'); }).get();
        expect(srcs).to.deep.equal([
          '//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js',
          '/testem.js',
          '//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js',
          'web' + path.sep + 'hello.js',
          'web' + path.sep + 'hello_tst.js'
        ]);
        done();
      });
    });

    it('gets testem.js', function(done) {
      request(baseUrl + '/testem.js', done);
    });

    it('gets src file', function(done) {
      assertUrlReturnsFileContents(baseUrl + 'web/hello.js', 'tests/web/hello.js', done);
    });

    it('gets bundled files', function(done) {
      assertUrlReturnsFileContents(baseUrl + 'testem/connection.html', 'public/testem/connection.html', done);
    });

    it('serves custom test page', function(done) {
      config.set('test_page', 'web/tests.html');
      assertUrlReturnsFileContents(baseUrl, 'tests/web/tests.html', done);
    });

    it('renders custom test page as template', function(done) {
      config.set('test_page', 'web/tests_template.mustache');
      request(baseUrl, function(err, req, text) {
        expect(text).to.equal(
          [
          '<!doctype html>',
          '',
          '',
          '    ',
          '    ',
          '',
          ''
          ].join('\n'));
        done();
      });
    });

    it('gets a file using a POST request', function(done) {
      request.post(baseUrl + 'web/hello.js', function(err, req, text) {
        expect(text).to.equal(fs.readFileSync('tests/web/hello.js').toString());
        done();
      });
    });

    it('lists directories', function(done) {
      request(baseUrl + 'data', function(err, req, text) {
        expect(text).to.match(/blah.txt<\/a>/);
        done();
      });
    });

    it('serves local content with browser ids', function(done) {
      assertUrlReturnsFileContents(baseUrl + '1234' + '/web/hello.js', 'tests/web/hello.js', done);
    });

    it('serves local content with tap id', function(done) {
      assertUrlReturnsFileContents(baseUrl + '-1' + '/web/hello.js', 'tests/web/hello.js', done);
    });

    it('accepts other http methods', function(done) {
      request.del(baseUrl + '-1' + '/web/hello.js', function(err, res) {
        expect(err).to.be.null();
        expect(res.statusCode).to.eq(200);
        done();
      });
    });

    describe('route', function() {
      it('routes server paths to local paths', function(done) {
        assertUrlReturnsFileContents(baseUrl + 'direct-test/test.js', 'tests/web/direct/test.js', done);
      });

      it('allows fallback paths', function(done) {
        var expectedCallbacks = 2;
        var cb = function() {
          if (--expectedCallbacks === 0) {
            done();
          }
        };
        assertUrlReturnsFileContents(baseUrl + 'fallback-test/test.js', 'tests/web/direct/test.js', cb);
        assertUrlReturnsFileContents(baseUrl + 'fallback-test/test2.js', 'tests/web/fallback/test2.js', cb);
      });
    });

    describe('proxies', function() {
      var api1, api2, api3, api4;

      beforeEach(function(done) {
        api1 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('API');
        });
        var options = {
          key: fs.readFileSync('tests/fixtures/certs/localhost.key'),
          cert: fs.readFileSync('tests/fixtures/certs/localhost.cert')
        };
        api2 = https.createServer(options, function(req, res) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('API - 2');
        });
        api3 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'application/json'});
          res.end(JSON.stringify({API: 3}));
        });

        api4 = http.createServer(function(req, res) {
          res.writeHead(200, {'Content-Type': 'application/json'});
          req.on('data', function(data) {
            res.write(data);
          });
          req.on('end', function() {
            res.end();
          });
        });

        api1.listen(13372, function() {
          api2.listen(13373, function() {
            api3.listen(13374, function() {
              api4.listen(13375, function() {
                done();
              });
            });
          });
        });
      });

      afterEach(function(done) {
        api1.close(function() {
          api2.close(function() {
            api3.close(function() {
              api4.close(function() {
                done();
              });
            });
          });
        });
      });

      it('proxies get request to api1', function(done) {
        request.get(baseUrl + 'api1/hello', function(err, req, text) {
          expect(text).to.equal('API');
          done();
        });
      });

      it('proxies get request to api2', function(done) {
        var options = {
          url: baseUrl + 'api2/hello',
          headers: {
            'Content-Type': 'application/json'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('API - 2');
          done();
        });
      });

      it('proxies post request to api1', function(done) {
        var options = {
          url: baseUrl + 'api1/hello',
          headers: {
            Accept: 'application/json'
          }
        };
        request.post(options, function(err, req, text) {
          expect(text).to.equal('API');
          done();
        });
      });

      it('proxies get request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'application/json'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('{"API":3}');
          done();
        });
      });

      it('proxies post request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'application/json'
          }
        };
        request.post(options, function(err, req, text) {
          expect(text).to.equal('{"API":3}');
          done();
        });
      });

      it('proxies post request to api4', function(done) {
        var options = {
          url: baseUrl + 'api4/test',
          headers: {
            Accept: 'application/json'
          },
          body: '{test: \'some value\'}'
        };
        request.post(options, function(err, req, text) {
          if (err) {
            return done(err);
          }
          expect(text).to.equal('{test: \'some value\'}');
          done();
        });
      });

      it('proxies get html request to api3', function(done) {
        var options = {
          url: baseUrl + 'api3/test',
          headers: {
            Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
          }
        };
        request.get(options, function(err, req, text) {
          expect(text).to.equal('Not found: /api3/test');
          done();
        });
      });
    });
  });

  describe('https', function() {
    before(function(done) {
      config = new Config('dev', {
        port: port,
        key: 'tests/fixtures/certs/localhost.key',
        cert: 'tests/fixtures/certs/localhost.cert',
        src_files: [
          'web/hello.js',
          {src:'web/hello_tst.js', attrs: ['data-foo="true"', 'data-bar']}
        ],
        cwd: 'tests'
      });
      baseUrl = 'https://localhost:' + port + '/';

      server = new Server(config);
      server.once('server-start', function() {
        done();
      });
      server.start();
    });
    after(function(done) {
      server.stop(function() {
        done();
      });
    });

    it('gets the home page', function(done) {
      request({ url: baseUrl, strictSSL: false }, done);
    });
  });

  describe('auto port assignment', function() {
    before(function(done) {
      config = new Config('dev', {
        port: 0,
        cwd: 'tests'
      });
      server = new Server(config);
      server.once('server-start', function() {
        done();
      });
      server.start();
    });
    after(function(done) {
      server.stop(function() {
        done();
      });
    });

    it('updates the config with the actual port', function() {
      expect(config.get('port')).not.to.eq(0);
      expect(config.get('port')).to.eq(server.server.address().port);
    });
  });
});

function assertUrlReturnsFileContents(url, file, done) {
  request(url, function(err, res, text) {
    expect(err).to.be.null();
    expect(res.statusCode).to.eq(200);
    expect(text).to.equal(fs.readFileSync(file).toString());
    done();
  });
}
<|endoftext|>


<commit_before>var browsers = {};
var secrets = {};
var browserNames = [];
if ((function () {
    try {
        secrets = require("./secrets");
        return true;
    } catch(e) {
        if (e.code !== "MODULE_NOT_FOUND") {
            throw e;
        }
        return process.env.BS_USERNAME && process.env.BS_AUTHKEY;
    }
})()) {
    browsers = require("./karma-browsers");
    browserNames = Object.keys(browsers);
    if (process.env.CIRCLE_NODE_TOTAL > 1) {
        browserNames.sort();
        var num = Math.ceil(browserNames.length / process.env.CIRCLE_NODE_TOTAL);
        browserNames = browserNames.slice(num * process.env.CIRCLE_NODE_INDEX, num * (process.env.CIRCLE_NODE_INDEX + 1));
    }
    browserNames.forEach(function(key) {
        browsers[key].base = "BrowserStack";
    });
}
var webpackConfig = require("./webpack.config");
var browserStackConfig = {
    username: process.env.BS_USERNAME || /["']?([^"']*)/.exec(secrets.BS_USERNAME)[1],
    accessKey: process.env.BS_AUTHKEY || /["']?([^"']*)/.exec(secrets.BS_AUTHKEY)[1],
    project: process.env.CIRCLE_PROJECT_REPONAME + "_" + process.env.CIRCLE_BRANCH || "frame_local",
    build: process.env.CIRCLE_BUILD_NUM || Date.now()
};
var fbServer = {};

module.exports = function(config) {
    config.set({
        frameworks: ["mocha"],
        client: {
            mocha: {
                timeout: 10000
            }
        },
        files: ["**/test/**/test_*.js"],
        exclude: ["**/node_modules/**/test/**/test_*.js"],
        preprocessors: {
            "**/test/**/test_*.js": ["webpack"]
        },
        autoWatchBatchDelay: 500,
        webpack: {
            module: webpackConfig.module,
            resolve: webpackConfig.resolve,
            plugins: webpackConfig.plugins,
            devtool: webpackConfig.devtool
        },
        middleware: ["firebaseServer"],
        concurrency: 1,
        browsers: browserNames,
        browserStack: browserStackConfig,
        customLaunchers: browsers
    });
};
<commit_msg>Karmaサーバーとの接続タイムアウトを20秒に延長しました。<commit_after>var browsers = {};
var secrets = {};
var browserNames = [];
if ((function () {
    try {
        secrets = require("./secrets");
        return true;
    } catch(e) {
        if (e.code !== "MODULE_NOT_FOUND") {
            throw e;
        }
        return process.env.BS_USERNAME && process.env.BS_AUTHKEY;
    }
})()) {
    browsers = require("./karma-browsers");
    browserNames = Object.keys(browsers);
    if (process.env.CIRCLE_NODE_TOTAL > 1) {
        browserNames.sort();
        var num = Math.ceil(browserNames.length / process.env.CIRCLE_NODE_TOTAL);
        browserNames = browserNames.slice(num * process.env.CIRCLE_NODE_INDEX, num * (process.env.CIRCLE_NODE_INDEX + 1));
    }
    browserNames.forEach(function(key) {
        browsers[key].base = "BrowserStack";
    });
}
var webpackConfig = require("./webpack.config");
var browserStackConfig = {
    username: process.env.BS_USERNAME || /["']?([^"']*)/.exec(secrets.BS_USERNAME)[1],
    accessKey: process.env.BS_AUTHKEY || /["']?([^"']*)/.exec(secrets.BS_AUTHKEY)[1],
    project: process.env.CIRCLE_PROJECT_REPONAME + "_" + process.env.CIRCLE_BRANCH || "frame_local",
    build: process.env.CIRCLE_BUILD_NUM || Date.now()
};
var fbServer = {};

module.exports = function(config) {
    config.set({
        frameworks: ["mocha"],
        client: {
            mocha: {
                timeout: 10000
            }
        },
        files: ["**/test/**/test_*.js"],
        exclude: ["**/node_modules/**/test/**/test_*.js"],
        preprocessors: {
            "**/test/**/test_*.js": ["webpack"]
        },
        autoWatchBatchDelay: 500,
        webpack: {
            module: webpackConfig.module,
            resolve: webpackConfig.resolve,
            plugins: webpackConfig.plugins,
            devtool: webpackConfig.devtool
        },
        middleware: ["firebaseServer"],
        concurrency: 1,
        browserNoActivityTimeout: 20000,
        browsers: browserNames,
        browserStack: browserStackConfig,
        customLaunchers: browsers
    });
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

const path = require('path');
const fs = require('fs');
const program = require('commander');
const package = require('../package.json');
const fetch = require('../lib/fetch');
const Server = require('../lib/Server');

/**
 * Create a server instance using the provided options.
 * @param  {Object} opts react-native-webpack-server options
 * @return {Server}      react-native-webpack-server server
 */
function createServer(opts) {
  opts.webpackConfigPath = path.resolve(process.cwd(), opts.webpackConfigPath);
  if (fs.existsSync(opts.webpackConfigPath)) {
    opts.webpackConfig = require(path.resolve(process.cwd(), opts.webpackConfigPath));
  } else {
    throw new Error('Must specify webpackConfigPath or create ./webpack.config.js');
  }
  delete opts.webpackConfigPath;

  const server = new Server(opts);
  return server;
}

function commonOptions(program) {
  return program
    .option(
      '-H, --hostname [hostname]',
      'Hostname on which the server will listen. [localhost]',
      'localhost'
    )
    .option(
      '-P, --port [port]',
      'Port on which the server will listen. [8080]',
      8080
    )
    .option(
      '-p, --packagerPort [port]',
      'Port on which the react-native packager will listen. [8081]',
      8081
    )
    .option(
      '-w, --webpackPort [port]',
      'Port on which the webpack dev server will listen. [8082]',
      8082
    )
    .option(
      '-c, --webpackConfigPath [path]',
      'Path to the webpack configuration file. [webpack.config.js]',
      'webpack.config.js'
    )
    .option(
      '-e, --entry [name]',
      'Webpack entry module. [index.ios]',
      'index.ios'
    );
}

program.version(package.version);

commonOptions(program.command('start'))
  .description('Start the webpack server.')
  .option('-r, --hot', 'Enable hot module replacement. [false]', false)
  .action(function(options) {
    const opts = options.opts();
    const server = createServer(opts);
    server.start();
  });

commonOptions(program.command('bundle'))
  .description('Bundle the app for distribution.')
  .option(
    '-b, --bundlePath [path]',
    'Path where the bundle should be written. [./iOS/main.jsbundle]',
    './iOS/main.jsbundle'
  )
  .option(
    '--no-optimize',
    'Whether the bundle should skip optimization. [false]',
    false
  )
  .action(function(options) {
    const opts = options.opts();
    const server = createServer(opts);
    const query = (opts.optimize) ? '?dev=false&minify=true' : '';
    const url = 'http://localhost:' + opts.port + '/index.ios.bundle' + query;
    const targetPath = path.resolve(opts.bundlePath);

    server.start();

    fetch(url).then(function(content) {
      fs.writeFileSync(targetPath, content);
      server.stop();

      // XXX: Hack something is keeping the process alive but we can still
      // safely kill here without leaving processes hanging around...
      process.exit(0);
    }).catch(function(err) {
      console.log('Error creating bundle...', err.stack);
      server.stop();
    });
  });

program.parse(process.argv);
<commit_msg>Use `url.format` to create bundle URL<commit_after>#!/usr/bin/env node

const path = require('path');
const fs = require('fs');
const url = require('url');
const program = require('commander');
const package = require('../package.json');
const fetch = require('../lib/fetch');
const Server = require('../lib/Server');

/**
 * Create a server instance using the provided options.
 * @param  {Object} opts react-native-webpack-server options
 * @return {Server}      react-native-webpack-server server
 */
function createServer(opts) {
  opts.webpackConfigPath = path.resolve(process.cwd(), opts.webpackConfigPath);
  if (fs.existsSync(opts.webpackConfigPath)) {
    opts.webpackConfig = require(path.resolve(process.cwd(), opts.webpackConfigPath));
  } else {
    throw new Error('Must specify webpackConfigPath or create ./webpack.config.js');
  }
  delete opts.webpackConfigPath;

  const server = new Server(opts);
  return server;
}

function commonOptions(program) {
  return program
    .option(
      '-H, --hostname [hostname]',
      'Hostname on which the server will listen. [localhost]',
      'localhost'
    )
    .option(
      '-P, --port [port]',
      'Port on which the server will listen. [8080]',
      8080
    )
    .option(
      '-p, --packagerPort [port]',
      'Port on which the react-native packager will listen. [8081]',
      8081
    )
    .option(
      '-w, --webpackPort [port]',
      'Port on which the webpack dev server will listen. [8082]',
      8082
    )
    .option(
      '-c, --webpackConfigPath [path]',
      'Path to the webpack configuration file. [webpack.config.js]',
      'webpack.config.js'
    )
    .option(
      '-e, --entry [name]',
      'Webpack entry module. [index.ios]',
      'index.ios'
    );
}

program.version(package.version);

commonOptions(program.command('start'))
  .description('Start the webpack server.')
  .option('-r, --hot', 'Enable hot module replacement. [false]', false)
  .action(function(options) {
    const opts = options.opts();
    const server = createServer(opts);
    server.start();
  });

commonOptions(program.command('bundle'))
  .description('Bundle the app for distribution.')
  .option(
    '-b, --bundlePath [path]',
    'Path where the bundle should be written. [./iOS/main.jsbundle]',
    './iOS/main.jsbundle'
  )
  .option(
    '--no-optimize',
    'Whether the bundle should skip optimization. [false]',
    false
  )
  .action(function(options) {
    const opts = options.opts();
    const server = createServer(opts);
    const query = opts.optimize ? {dev: false, minify: true} : {};
    const bundleUrl = url.format({
      protocol: 'http',
      hostname: 'localhost',
      port: opts.port,
      pathname: 'index.ios.bundle',
      query: query,
    });
    const targetPath = path.resolve(opts.bundlePath);

    server.start();

    fetch(bundleUrl).then(function(content) {
      fs.writeFileSync(targetPath, content);
      server.stop();

      // XXX: Hack something is keeping the process alive but we can still
      // safely kill here without leaving processes hanging around...
      process.exit(0);
    }).catch(function(err) {
      console.log('Error creating bundle...', err.stack);
      server.stop();
    });
  });

program.parse(process.argv);
<|endoftext|>


<commit_before>$(document).ready(function(){

	$("#addStop-edit").click(function(){
    	$("#stoplist-edit").append('

<input class="stopname" type="text" placeholder="Haltestellenname" />

')
  	});

   	$("#deleteStop-edit").click(function(){
	    if($("#stoplist-edit").children().last().attr("id")!="first-edit"){
	      $("#stoplist-edit").children().last().remove();
	    }
  	});

	$("#changeIconEdit").click(function(){
		$('.view').each(function(index) {
    	if($(this).attr('id') == 'chooseIcon_view') {
      	$(this).show();
      }else {
      	$(this).hide();
      }
		});
		$(".iconDiv").click(function(event){
			var icon = event.target.id;
			if(icon!=""){
				console.log(icon);
				$("#icon-edit").remove();
				$("#iconContainer-edit").append('

');

				$('.view').each(function(index) {
    			if($(this).attr('id') == 'routeEdit_view') {
      			$(this).show();
      		}else {
      			$(this).hide();
      		}
				});
			}
		});
	});

	// page needs to be reloaded for same reasons as in route.js
	$("#backbtnEdit").click(function(){
		location.reload();
	});
});
<commit_msg>Seems to apply StopAdd/Delete twice<commit_after>$(document).ready(function(){

	$("#addStop-edit").click(function(){
    	$("#stoplist-edit").append('

<input class="stopname" type="text" placeholder="Haltestellenname" />

')
  	});

   	$("#deleteStop-edit").click(function(){
	    if($("#stoplist-edit").children().last().attr("id")!="first-edit"){
	      $("#stoplist-edit").children().last().remove();
	    }
  	});

	$("#changeIconEdit").click(function(){
		$('.view').each(function(index) {
    	if($(this).attr('id') == 'chooseIcon_view') {
      	$(this).show();
      }else {
      	$(this).hide();
      }
		});
		$(".iconDiv").click(function(event){
			var icon = event.target.id;
			if(icon!=""){
				console.log(icon);
				$("#icon-edit").remove();
				$("#iconContainer-edit").append('

');
				$('.view').each(function(index) {
    			if($(this).attr('id') == 'routeEdit_view') {
      			$(this).show();
      		}else {
      			$(this).hide();
      		}
				});
			}
		});
	});

	// page needs to be reloaded for same reasons as in route.js
	$("#backbtnEdit").click(function(){
		location.reload();
	});
});
<|endoftext|>


<commit_before>define([
	"dojo",
	"..",
	"./ValidationTextBox",
	"./ComboBoxMixin"], function(dojo, dijit){

	// module:
	//		dijit/form/ComboBox
	// summary:
	//		Auto-completing text box, and base class for dijit.form.FilteringSelect.

	dojo.declare(
		"dijit.form.ComboBox",
		[dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
		{
			// summary:
			//		Auto-completing text box, and base class for dijit.form.FilteringSelect.
			//
			// description:
			//		The drop down box's values are populated from an class called
			//		a data provider, which returns a list of values based on the characters
			//		that the user has typed into the input box.
			//		If OPTION tags are used as the data provider via markup,
			//		then the OPTION tag's child text node is used as the widget value
			//		when selected.  The OPTION tag's value attribute is ignored.
			//		To set the default value when using OPTION tags, specify the selected
			//		attribute on 1 of the child OPTION tags.
			//
			//		Some of the options to the ComboBox are actually arguments to the data
			//		provider.
		}
	);

	return dijit.form.ComboBox;
});
<commit_msg>more indentation fixes, refs #12672<commit_after>define([
	"dojo",
	"..",
	"./ValidationTextBox",
	"./ComboBoxMixin"], function(dojo, dijit){

	// module:
	//		dijit/form/ComboBox
	// summary:
	//		Auto-completing text box, and base class for dijit.form.FilteringSelect.

	dojo.declare("dijit.form.ComboBox", [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], {
		// summary:
		//		Auto-completing text box, and base class for dijit.form.FilteringSelect.
		//
		// description:
		//		The drop down box's values are populated from an class called
		//		a data provider, which returns a list of values based on the characters
		//		that the user has typed into the input box.
		//		If OPTION tags are used as the data provider via markup,
		//		then the OPTION tag's child text node is used as the widget value
		//		when selected.  The OPTION tag's value attribute is ignored.
		//		To set the default value when using OPTION tags, specify the selected
		//		attribute on 1 of the child OPTION tags.
		//
		//		Some of the options to the ComboBox are actually arguments to the data
		//		provider.
	});

	return dijit.form.ComboBox;
});
<|endoftext|>


<commit_before>/**
 * This module handles all clientside searching
 */

const config = require('collections-online/shared/config');
const helpers = require('../../../shared/helpers');

const MapController = require('map-controller');
require('./search-freetext-form');

const getSearchParams = require('./get-parameters');
const elasticsearchQueryBody = require('./es-query-body');
const elasticsearchAggregationsBody = require('./es-aggregations-body');
const generateQuerystring = require('./generate-querystring');
const resultsHeader = require('./results-header');
const sorting = require('./sorting');
const navigator = require('../document/navigator');

const templates = {
  searchResultItem: require('views/includes/search-results-item')
};

// How many assets should be loaded at once?
const PAGE_SIZE = 24;
module.exports.PAGE_SIZE = PAGE_SIZE;

let resultsDesired = PAGE_SIZE;
let resultsLoaded = [];
let resultsTotal = Number.MAX_SAFE_INTEGER;
let loadingResults = false;

// Controls whether we should fetch search-results for a map or a list.
let viewMode = 'list';

let elasticsearch = require('elasticsearch');
let es = new elasticsearch.Client({
  host: location.origin + '/api'
});

function initialize() {
  const $searchInput = $('.search-freetext-form__input');
  const $results = $('#results');
  // Button shown at the end of the list search result that triggers the load
  // of more results.
  const $loadMoreBtn = $('#load-more-btn');
  const $noResultsText = $('#no-results-text');

  function reset() {
    resultsLoaded = [];
    resultsTotal = Number.MAX_SAFE_INTEGER;
    resultsDesired = PAGE_SIZE;
    $(window).off('scroll');
    $loadMoreBtn.addClass('invisible');
  }

  /**
   * Perform a search and update the map or list.
   *
   * @param updateWidgets
   *   Whether this update should trigger refresh of the related widgets.
   *
   * @param indicateLoading
   *   Whether to show a overlay as the results are loading.
   *
   * @param searchParams
   *   Optional override search parameters.
   */
  function update(updateWidgets, indicateLoading, searchParams) {
    // If the indicateLoading is not set - default to true
    if(typeof(indicateLoading) === 'undefined') {
      indicateLoading = true;
    }

    // Get search parameters if they have not been provided.
    if (!searchParams) {
      searchParams = getSearchParams();
    }

    // Update the filter-bar.
    sorting.update(searchParams);

    // Update the freetext search input, q contains the querystring.
    $searchInput.val(searchParams.filters.q);

    // Update the page title
    const title = helpers.generateSearchTitle(searchParams.filters);
    $('head title').text(title + ' - ' + config.siteTitle);
    loadingResults = true;

    // A fresh update is the first of potentially many updates with the same
    // search parameters.
    updateWidgets = resultsLoaded.length === 0 || updateWidgets;

    if(updateWidgets) {
      if(config.features.filterSidebar) {
        const sidebar = require('./filter-sidebar');
        // Update the sidebar right away, as we're going of doing async work
        // with searchParams, work on a clone.
        const clonedParams = JSON.parse(JSON.stringify(searchParams));
        sidebar.update(clonedParams.filters, null);
        // Get aggragations for the sidebar
        es.search({
          body: elasticsearchAggregationsBody(clonedParams),
          size: 0
        }).then(function (response) {
          sidebar.update(clonedParams.filters, response.aggregations);
        }, function (error) {
          console.trace(error.message);
        });
      }
    }

    let resultCallback = function (resultsTotal) {
      // Update the results header with the result
      if(updateWidgets) {
        resultsHeader.update(searchParams, resultsTotal);
        if(indicateLoading) {
          $('.search-results').removeClass('search-results--loading');
        }
      }
    };

    // Do search depending on current viewmode.
    if (viewMode === 'map') {
      mapController.onUpdate(searchParams, function(updatedParams){
        // Generate the query body, this will inject a bounds filter if we added
        // one above.
        let queryBody = elasticsearchQueryBody(updatedParams);

        // Let plugins modify the search body.
        if(typeof(helpers.modifySearchQueryBody) === 'function') {
          // If a modifySearchQueryBody helper is defined, call it
          queryBody = helpers.modifySearchQueryBody(queryBody, updatedParams);
        }

        if (updatedParams.geohash) {
          queryBody.aggregations = {
            'geohash_grid' : {
              'geohash_grid' : {
                'field' : 'location',
                'precision' : config.search.geohashPrecision
              }
            }
          };
        }

        const maxAssets = config.search.maxAssetMarkers;
        // We've configured our search, now setup the query and execute it.
        const searchObject = {
          body: queryBody,
          // We only want the aggregation so we don't care about the hits.
          size: updatedParams.geohash ? 0 : maxAssets,
          _source: [
            'location',
            'longitude',
            'latitude',
            'collection',
            'id',
            'short_title',
            'type',
            'heading',
            'description'
          ],
        };

        // Make sure we persist, but replace state so that we don't end up in
        // the back/forward history.
        persistChangedParams(updatedParams, true);

        es.search(searchObject).then(function (response) {
          resultsTotal = response.hits.total;
          loadingResults = false;
          mapController.onResults(response, updatedParams);
        }, function (error) {
          console.trace(error.message);
        });
      });
    } else if (viewMode === 'list') {
      if (updateWidgets) {
        // Start updating the results header
        resultsHeader.update(searchParams, resultsTotal);
        // Update the results header before the result comes in
        if(indicateLoading) {
          $('.search-results').addClass('search-results--loading');
        }
      }
      resultsTotal = updateList(searchParams, updateWidgets, resultCallback);
    }
  }

  /**
   * Update the search result list.
   */
  function updateList(searchParams, updateWidgets, resultCallback) {
    // Generate the query body
    let queryBody = elasticsearchQueryBody(searchParams);

    if(typeof(helpers.modifySearchQueryBody) === 'function') {
      // If a modifySearchQueryBody helper is defined, call it
      queryBody = helpers.modifySearchQueryBody(queryBody, searchParams);
    }

    const searchObject = {
      body: queryBody,
      from: resultsLoaded.length,
      _source: ['collection', 'id', 'short_title', 'type', 'description'],
      size: resultsDesired - resultsLoaded.length
    };

    // Pull in the search results.
    es.search(searchObject).then(function (response) {
      // If no results are loaded yet, it might be because we just called reset
      if(resultsLoaded.length === 0) {
        // Remove all search result items from $results, that might be there
        $results.find('.search-results-item').remove();
      }
      resultsTotal = response.hits.total;
      loadingResults = false;

      response.hits.hits.forEach(function(hit) {
        const item = {
          type: hit._type,
          metadata: hit._source
        };
        const markup = templates.searchResultItem(item);
        $results.append(markup);
        resultsLoaded.push(item);
      });

      // Save the results loaded in the session storage, so we can use them on
      // out other places.
      navigator.save({
        resultsLoaded, queryBody
      });

      // Replace the state of in the history if supported
      if(history.replaceState) {
        history.replaceState({
          resultsLoaded,
          resultsTotal
        }, null, null);
      }

      // Show some text if we don't have any results
      if (resultsTotal === 0) {
        $noResultsText.removeClass('hidden');
      } else {
        $noResultsText.addClass('hidden');
      }

      // If we have not loaded all available results, let's show the btn to load
      if(updateWidgets && resultsLoaded.length < resultsTotal) {
        $loadMoreBtn.removeClass('invisible');
      } else {
        $loadMoreBtn.addClass('invisible');
      }

      resultCallback(resultsTotal);
    }, function (error) {
      console.trace(error.message);
    });
  }

  /**
   * Store changed params into the browser history.
   *
   * @param searchParams
   *   The search params to persist.
   * @param replaceState
   *   Whether to replace state or push a new (the latter lets the browser move
   *   back and forth between states).
   */
  function persistChangedParams (searchParams, replaceState = false) {
    // Change the URL
    if (history) {
      reset();
      // Update the browser history and the url with our new parameters.

      var state = {searchParams: searchParams};
      var url = location.pathname + generateQuerystring(searchParams);
      if (replaceState) {
        history.replaceState(state, '', url);
      }
      else {
        history.pushState(state, '', url);
      }
    }
    else {
      throw new Error('History API is required');
    }
  }

  function enableEndlessScrolling() {
    $loadMoreBtn.addClass('invisible');
    $(window).on('scroll', function(e) {
      var $lastResult = $('#results .search-results-item:last-child');
      if($lastResult.length > 0) {
        var lastResultOffset = $lastResult.offset();
        var scrollTop = $(window).scrollTop();
        var scrollBottom = scrollTop + $(window).height();
        if(scrollBottom > lastResultOffset.top && !loadingResults) {
          resultsDesired += PAGE_SIZE;
          update();
        }
      }
    }).scroll();
  }

  /**
   * Check if the user has any relevant history data during init and use it.
   *
   * @param state
   *   The history.state.
   */
  function inflateHistoryState(state) {
    // Render results from the state
    if(state.resultsLoaded) {
      reset();
      // Remove all the search result items right away
      $results.find('.search-results-item').remove();

      // Append rendered markup, once per asset loaded from the state.
      resultsLoaded = state.resultsLoaded;
      resultsDesired = resultsLoaded.length;
      resultsLoaded.forEach(function(item) {
        var markup = templates.searchResultItem(item);
        $results.append(markup);
      });

      // Replace the resultsTotal from the state
      resultsTotal = state.resultsTotal;

      // Using the updateWidgets=true, updates the header as well
      // Using the indicateLoading=false makes sure the UI doesn't blink
      update(true, false);
    }
  }

  // When the user navigates the state, update it
  window.addEventListener('popstate', function(event) {
    inflateHistoryState(event.state);
  }, false);

  const searchControllerCallbacks = {
    // Allow the caller to refresh the current search-results.
    refresh: function() {
      update(true, false);
    },

    getCurrentSearchParameters: function (){
      return getSearchParams();
    },
  };

  // Get any initial parameters from the url and pass the relevant part to the
  // map.
  var options = {};

  // Set initial center if specified via config.
  if (config.geoTagging && config.geoTagging.initialCenter && config.geoTagging.initialCenter['lon'] && config.geoTagging.initialCenter['lat']) {
    options.initialCenter = [
      config.geoTagging.initialCenter['lon'],
      config.geoTagging.initialCenter['lat']
    ];
  }

  var searchParams = getSearchParams();
  if (searchParams.map) {
    options.mapInitParam = searchParams.map;
  }
  // Setup the map with a callback it can use when it needs a new search
  // triggered and a configuration for when we switch between hash and asset
  // search-results.
  const mapController = MapController(document.getElementById('map'), searchControllerCallbacks, options);

  // *** Register event handlers ***
  $('#sidebar, #sidebarmobile, #filters, #filtersmobile').on('click', '.btn', function() {
    var action = $(this).data('action');
    var field = $(this).data('field');
    var filter = config.search.filters[field];
    var value = $(this).data('value');
    if(!value && filter.type === 'date-interval-range') {
      var $form = $(this).closest('.form-group');
      var from = $form.find('[name='+field+'-from]').val() || '*';
      var to = $form.find('[name='+field+'-to]').val() || '*';
      if(from !== '*' || to !== '*') {
        value = from.replace(/-/g, '/') + '-' + to.replace(/-/g, '/');
      } else {
        return;
      }
    }
    var searchParams = getSearchParams();
    var filters = searchParams.filters;
    if(action === 'add-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field].push(value);
      } else {
        filters[field] = [value];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    } else if(action === 'remove-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field] = filters[field].filter(function(v) {
          return v !== value;
        });
      } else {
        delete filters[field];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    }
  });

  $('#sorting-menu').on('click', '.dropdown__options a', function() {
    var sorting = $(this).data('value');
    var searchParams = getSearchParams();
    searchParams.sorting = sorting;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  // Enabled the load-more button
  $loadMoreBtn.on('click', function() {
    enableEndlessScrolling();
  });

  // Toggle filtersection visibility on mobile
  $('#sidebar, #sidebarmobile').on('click', '[data-action="show-filters"]', function() {
    var filterSection = $(this).data('id') + '-filters';
    var $filterSection = $('[data-id="' + filterSection + '"]');
    var wasExpanded = $(this).hasClass('expanded');
    var visibleClass = 'search-filter-sidebar__filters--expanded';

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $filterSection.addClass(visibleClass);
    } else {
      $(this).removeClass('expanded');
      $filterSection.removeClass(visibleClass);
    }
  });

  // Toggle filterbar menus
  $('.filterbar--mobile__button--filters').on('click', function() {
    $('body').addClass('has-filter-open');
  });

  $('.filterbar--mobile__button--close').on('click', function() {
    $('body').removeClass('has-filter-open');
  });

  // When the view-mode is changed by the view-mode selector (view-mode.js),
  // store the new value and trigger a search update.
  $('body').on('search:viewModeChanged', function(e, eventViewMode) {
    viewMode = eventViewMode;
    $('body').trigger('search:update');
  });

  // Let anyone with access to the body element trigger search-updates.
  $('body').on('search:update', function() {
    update(true, true);
  });

  $('.search-filter-sidebar__tab').on('click', '[data-action="show-filterbar-menu"]', function() {
    var wasExpanded = $(this).hasClass('expanded');
    var $parentItem = $(this).closest('.filterbar__item');
    var $filterbar = $(this).closest('.filterbar');

    $filterbar.find('.filterbar__menu').hide();
    $filterbar.find('.expanded').each(function() {
      $(this).removeClass('expanded');
    });

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $parentItem.find('.filterbar__tab').addClass('expanded');
      $parentItem.find('.filterbar__menu').show();
    } else {
      $(this).removeClass('expanded');
      $parentItem.find('.filterbar__tab').removeClass('expanded');
      $parentItem.find('.filterbar__menu').hide();
    }
  });

  // Take control over the search form.
  $searchInput.closest('form').submit(function(e) {
    e.preventDefault();
    var queryString = $searchInput.val() || '';
    var searchParams = getSearchParams();
    searchParams.filters.q = queryString;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  const currentParams = getSearchParams();
  if (currentParams.map) {
    // If the url contains a map parameter, set us in map mode.
    $('body').trigger('search:viewModeChanged', ['map']);
  } else {
    // Default view-mode, so no need to switch, just go ahead and do a search.
    update();
  }
}

// If the path is right - let's initialize
if(decodeURIComponent(location.pathname) === '/' + config.search.path) {
  $(initialize);
}
<commit_msg>Move the call to the map-controller up in the beginning of the update<commit_after>/**
 * This module handles all clientside searching
 */

const config = require('collections-online/shared/config');
const helpers = require('../../../shared/helpers');

const MapController = require('map-controller');
require('./search-freetext-form');

const getSearchParams = require('./get-parameters');
const elasticsearchQueryBody = require('./es-query-body');
const elasticsearchAggregationsBody = require('./es-aggregations-body');
const generateQuerystring = require('./generate-querystring');
const resultsHeader = require('./results-header');
const sorting = require('./sorting');
const navigator = require('../document/navigator');

const templates = {
  searchResultItem: require('views/includes/search-results-item')
};

// How many assets should be loaded at once?
const PAGE_SIZE = 24;
module.exports.PAGE_SIZE = PAGE_SIZE;

let resultsDesired = PAGE_SIZE;
let resultsLoaded = [];
let resultsTotal = Number.MAX_SAFE_INTEGER;
let loadingResults = false;

// Controls whether we should fetch search-results for a map or a list.
let viewMode = 'list';

let elasticsearch = require('elasticsearch');
let es = new elasticsearch.Client({
  host: location.origin + '/api'
});

function initialize() {
  const $searchInput = $('.search-freetext-form__input');
  const $results = $('#results');
  // Button shown at the end of the list search result that triggers the load
  // of more results.
  const $loadMoreBtn = $('#load-more-btn');
  const $noResultsText = $('#no-results-text');

  function reset() {
    resultsLoaded = [];
    resultsTotal = Number.MAX_SAFE_INTEGER;
    resultsDesired = PAGE_SIZE;
    $(window).off('scroll');
    $loadMoreBtn.addClass('invisible');
  }

  /**
   * Perform a search and update the map or list.
   *
   * @param updateWidgets
   *   Whether this update should trigger refresh of the related widgets.
   *
   * @param indicateLoading
   *   Whether to show a overlay as the results are loading.
   *
   * @param searchParams
   *   Optional override search parameters.
   */
  function update(updateWidgets, indicateLoading, searchParams) {
    // If the indicateLoading is not set - default to true
    if(typeof(indicateLoading) === 'undefined') {
      indicateLoading = true;
    }

    // If we've not explicitly been passed searchParameters (which right now
    // only happens when the map calls us back after adding a geo bounding-
    // box), get it from the url, and see if we should have the mapcontroller
    // update it.
    if (!searchParams) {
      // Get search parameters from the url.
      searchParams = getSearchParams();

      // Let the map alter our search-parameters if they have not been
      // overridden and have it call us back with the update parameters.
      if (viewMode === 'map') {
        mapController.onUpdate(searchParams, function(updatedParams){
          update(updateWidgets, indicateLoading, updatedParams);
        });
        return;
      }
    }

    // Update the sort-menu in the filter-bar.
    sorting.update(searchParams);

    // Update the freetext search input, q contains the querystring.
    $searchInput.val(searchParams.filters.q);

    // Update the page title
    const title = helpers.generateSearchTitle(searchParams.filters);
    $('head title').text(title + ' - ' + config.siteTitle);
    loadingResults = true;

    // A fresh update is the first of potentially many updates with the same
    // search parameters.
    updateWidgets = resultsLoaded.length === 0 || updateWidgets;

    if(updateWidgets) {
      if(config.features.filterSidebar) {
        const sidebar = require('./filter-sidebar');
        // Update the sidebar right away, as we're going of doing async work
        // with searchParams, work on a clone.
        const clonedSearchParams = JSON.parse(JSON.stringify(searchParams));
        sidebar.update(clonedSearchParams.filters, null);
        // Get aggragations for the sidebar
        es.search({
          body: elasticsearchAggregationsBody(clonedSearchParams),
          size: 0
        }).then(function (response) {
          sidebar.update(clonedSearchParams.filters, response.aggregations);
        }, function (error) {
          console.trace(error.message);
        });
      }
    }

    let resultCallback = function (resultsTotal) {
      // Update the results header with the result
      if(updateWidgets) {
        resultsHeader.update(searchParams, resultsTotal);
        if(indicateLoading) {
          $('.search-results').removeClass('search-results--loading');
        }
      }
    };

    // Do search depending on current viewmode.
    if (viewMode === 'map') {
      // Generate the query body, this will inject a bounds filter if we added
      // one above.
      let queryBody = elasticsearchQueryBody(searchParams);

      // Let plugins modify the search body.
      if(typeof(helpers.modifySearchQueryBody) === 'function') {
        // If a modifySearchQueryBody helper is defined, call it
        queryBody = helpers.modifySearchQueryBody(queryBody, searchParams);
      }

      if (searchParams.geohash) {
        queryBody.aggregations = {
          'geohash_grid' : {
            'geohash_grid' : {
              'field' : 'location',
              'precision' : config.search.geohashPrecision
            }
          }
        };
      }

      const maxAssets = config.search.maxAssetMarkers;
      // We've configured our search, now setup the query and execute it.
      const searchObject = {
        body: queryBody,
        // We only want the aggregation so we don't care about the hits.
        size: searchParams.geohash ? 0 : maxAssets,
        _source: [
          'location',
          'longitude',
          'latitude',
          'collection',
          'id',
          'short_title',
          'type',
          'heading',
          'description'
        ],
      };

      // Make sure we persist, but replace state so that we don't end up in
      // the back/forward history.
      persistChangedParams(searchParams, true);

      es.search(searchObject).then(function (response) {
        resultsTotal = response.hits.total;
        loadingResults = false;
        mapController.onResults(response, searchParams);
      }, function (error) {
        console.trace(error.message);
      });
    } else if (viewMode === 'list') {
      if (updateWidgets) {
        // Start updating the results header
        resultsHeader.update(searchParams, resultsTotal);
        // Update the results header before the result comes in
        if(indicateLoading) {
          $('.search-results').addClass('search-results--loading');
        }
      }
      resultsTotal = updateList(searchParams, updateWidgets, resultCallback);
    }
  }

  /**
   * Update the search result list.
   */
  function updateList(searchParams, updateWidgets, resultCallback) {
    // Generate the query body
    let queryBody = elasticsearchQueryBody(searchParams);

    if(typeof(helpers.modifySearchQueryBody) === 'function') {
      // If a modifySearchQueryBody helper is defined, call it
      queryBody = helpers.modifySearchQueryBody(queryBody, searchParams);
    }

    const searchObject = {
      body: queryBody,
      from: resultsLoaded.length,
      _source: ['collection', 'id', 'short_title', 'type', 'description'],
      size: resultsDesired - resultsLoaded.length
    };

    // Pull in the search results.
    es.search(searchObject).then(function (response) {
      // If no results are loaded yet, it might be because we just called reset
      if(resultsLoaded.length === 0) {
        // Remove all search result items from $results, that might be there
        $results.find('.search-results-item').remove();
      }
      resultsTotal = response.hits.total;
      loadingResults = false;

      response.hits.hits.forEach(function(hit) {
        const item = {
          type: hit._type,
          metadata: hit._source
        };
        const markup = templates.searchResultItem(item);
        $results.append(markup);
        resultsLoaded.push(item);
      });

      // Save the results loaded in the session storage, so we can use them on
      // out other places.
      navigator.save({
        resultsLoaded, queryBody
      });

      // Replace the state of in the history if supported
      if(history.replaceState) {
        history.replaceState({
          resultsLoaded,
          resultsTotal
        }, null, null);
      }

      // Show some text if we don't have any results
      if (resultsTotal === 0) {
        $noResultsText.removeClass('hidden');
      } else {
        $noResultsText.addClass('hidden');
      }

      // If we have not loaded all available results, let's show the btn to load
      if(updateWidgets && resultsLoaded.length < resultsTotal) {
        $loadMoreBtn.removeClass('invisible');
      } else {
        $loadMoreBtn.addClass('invisible');
      }

      resultCallback(resultsTotal);
    }, function (error) {
      console.trace(error.message);
    });
  }

  /**
   * Store changed params into the browser history.
   *
   * @param searchParams
   *   The search params to persist.
   * @param replaceState
   *   Whether to replace state or push a new (the latter lets the browser move
   *   back and forth between states).
   */
  function persistChangedParams (searchParams, replaceState = false) {
    // Change the URL
    if (history) {
      reset();
      // Update the browser history and the url with our new parameters.

      var state = {searchParams: searchParams};
      var url = location.pathname + generateQuerystring(searchParams);
      if (replaceState) {
        history.replaceState(state, '', url);
      }
      else {
        history.pushState(state, '', url);
      }
    }
    else {
      throw new Error('History API is required');
    }
  }

  function enableEndlessScrolling() {
    $loadMoreBtn.addClass('invisible');
    $(window).on('scroll', function(e) {
      var $lastResult = $('#results .search-results-item:last-child');
      if($lastResult.length > 0) {
        var lastResultOffset = $lastResult.offset();
        var scrollTop = $(window).scrollTop();
        var scrollBottom = scrollTop + $(window).height();
        if(scrollBottom > lastResultOffset.top && !loadingResults) {
          resultsDesired += PAGE_SIZE;
          update();
        }
      }
    }).scroll();
  }

  /**
   * Check if the user has any relevant history data during init and use it.
   *
   * @param state
   *   The history.state.
   */
  function inflateHistoryState(state) {
    // Render results from the state
    if(state.resultsLoaded) {
      reset();
      // Remove all the search result items right away
      $results.find('.search-results-item').remove();

      // Append rendered markup, once per asset loaded from the state.
      resultsLoaded = state.resultsLoaded;
      resultsDesired = resultsLoaded.length;
      resultsLoaded.forEach(function(item) {
        var markup = templates.searchResultItem(item);
        $results.append(markup);
      });

      // Replace the resultsTotal from the state
      resultsTotal = state.resultsTotal;

      // Using the updateWidgets=true, updates the header as well
      // Using the indicateLoading=false makes sure the UI doesn't blink
      update(true, false);
    }
  }

  // When the user navigates the state, update it
  window.addEventListener('popstate', function(event) {
    inflateHistoryState(event.state);
  }, false);

  const searchControllerCallbacks = {
    // Allow the caller to refresh the current search-results.
    refresh: function() {
      update(true, false);
    },

    getCurrentSearchParameters: function (){
      return getSearchParams();
    },
  };

  // Get any initial parameters from the url and pass the relevant part to the
  // map.
  var options = {};

  // Set initial center if specified via config.
  if (config.geoTagging && config.geoTagging.initialCenter && config.geoTagging.initialCenter['lon'] && config.geoTagging.initialCenter['lat']) {
    options.initialCenter = [
      config.geoTagging.initialCenter['lon'],
      config.geoTagging.initialCenter['lat']
    ];
  }

  var searchParams = getSearchParams();
  if (searchParams.map) {
    options.mapInitParam = searchParams.map;
  }
  // Setup the map with a callback it can use when it needs a new search
  // triggered and a configuration for when we switch between hash and asset
  // search-results.
  const mapController = MapController(document.getElementById('map'), searchControllerCallbacks, options);

  // *** Register event handlers ***
  $('#sidebar, #sidebarmobile, #filters, #filtersmobile').on('click', '.btn', function() {
    var action = $(this).data('action');
    var field = $(this).data('field');
    var filter = config.search.filters[field];
    var value = $(this).data('value');
    if(!value && filter.type === 'date-interval-range') {
      var $form = $(this).closest('.form-group');
      var from = $form.find('[name='+field+'-from]').val() || '*';
      var to = $form.find('[name='+field+'-to]').val() || '*';
      if(from !== '*' || to !== '*') {
        value = from.replace(/-/g, '/') + '-' + to.replace(/-/g, '/');
      } else {
        return;
      }
    }
    var searchParams = getSearchParams();
    var filters = searchParams.filters;
    if(action === 'add-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field].push(value);
      } else {
        filters[field] = [value];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    } else if(action === 'remove-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field] = filters[field].filter(function(v) {
          return v !== value;
        });
      } else {
        delete filters[field];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    }
  });

  $('#sorting-menu').on('click', '.dropdown__options a', function() {
    var sorting = $(this).data('value');
    var searchParams = getSearchParams();
    searchParams.sorting = sorting;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  // Enabled the load-more button
  $loadMoreBtn.on('click', function() {
    enableEndlessScrolling();
  });

  // Toggle filtersection visibility on mobile
  $('#sidebar, #sidebarmobile').on('click', '[data-action="show-filters"]', function() {
    var filterSection = $(this).data('id') + '-filters';
    var $filterSection = $('[data-id="' + filterSection + '"]');
    var wasExpanded = $(this).hasClass('expanded');
    var visibleClass = 'search-filter-sidebar__filters--expanded';

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $filterSection.addClass(visibleClass);
    } else {
      $(this).removeClass('expanded');
      $filterSection.removeClass(visibleClass);
    }
  });

  // Toggle filterbar menus
  $('.filterbar--mobile__button--filters').on('click', function() {
    $('body').addClass('has-filter-open');
  });

  $('.filterbar--mobile__button--close').on('click', function() {
    $('body').removeClass('has-filter-open');
  });

  // When the view-mode is changed by the view-mode selector (view-mode.js),
  // store the new value and trigger a search update.
  $('body').on('search:viewModeChanged', function(e, eventViewMode) {
    viewMode = eventViewMode;
    $('body').trigger('search:update');
  });

  // Let anyone with access to the body element trigger search-updates.
  $('body').on('search:update', function() {
    update(true, true);
  });

  $('.search-filter-sidebar__tab').on('click', '[data-action="show-filterbar-menu"]', function() {
    var wasExpanded = $(this).hasClass('expanded');
    var $parentItem = $(this).closest('.filterbar__item');
    var $filterbar = $(this).closest('.filterbar');

    $filterbar.find('.filterbar__menu').hide();
    $filterbar.find('.expanded').each(function() {
      $(this).removeClass('expanded');
    });

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $parentItem.find('.filterbar__tab').addClass('expanded');
      $parentItem.find('.filterbar__menu').show();
    } else {
      $(this).removeClass('expanded');
      $parentItem.find('.filterbar__tab').removeClass('expanded');
      $parentItem.find('.filterbar__menu').hide();
    }
  });

  // Take control over the search form.
  $searchInput.closest('form').submit(function(e) {
    e.preventDefault();
    var queryString = $searchInput.val() || '';
    var searchParams = getSearchParams();
    searchParams.filters.q = queryString;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  const currentParams = getSearchParams();
  if (currentParams.map) {
    // If the url contains a map parameter, set us in map mode.
    $('body').trigger('search:viewModeChanged', ['map']);
  } else {
    // Default view-mode, so no need to switch, just go ahead and do a search.
    update();
  }
}

// If the path is right - let's initialize
if(decodeURIComponent(location.pathname) === '/' + config.search.path) {
  $(initialize);
}
<|endoftext|>


<commit_before>const externals = require('./externals.conf.js');

module.exports = function(config) {
  const files = [
    'node_modules/sinon/pkg/sinon.js',
    'node_modules/react/dist/react-with-addons.min.js',
    'node_modules/react-dom/dist/react-dom.min.js',
    'node_modules/hammerjs/hammer.min.js',
    'node_modules/syn/dist/global/syn.js',
    'browser/noflo-ui.min.js',
    'spec/utils/*.js',
    'spec/*.js',
    'spec/browser/*.js',
    {
      pattern: 'spec/mockruntime.html',
      included: false,
      served: true,
      watched: true,
    },
    {
      pattern: 'index.html',
      included: false,
      served: true,
      watched: true,
    },
  ];
  const serveExternals = externals.slice(0);
  serveExternals.push('themes/*.css');
  serveExternals.push('css/*.css');
  serveExternals.forEach((pattern) => {
    if (files.indexOf(pattern) !== -1) {
      return;
    }
    files.push({
      pattern,
      included: false,
      served: true,
      watched: false,
    });
  });

  const configuration = {
    basePath: '',
    frameworks: ['mocha', 'chai'],
    files,
    exclude: [],
    preprocessors: {},
    reporters: ['mocha'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['ChromeHeadless'],
    customLaunchers: {
      ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox'],
      },
    },
    singleRun: true,
    concurrency: Infinity
  };

  if (process.env.TRAVIS) {
    configuration.browsers = ['ChromeHeadlessNoSandbox'];
  }

  config.set(configuration);
}
<commit_msg>Local chrome as needed<commit_after>const externals = require('./externals.conf.js');

process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  const files = [
    'node_modules/sinon/pkg/sinon.js',
    'node_modules/react/dist/react-with-addons.min.js',
    'node_modules/react-dom/dist/react-dom.min.js',
    'node_modules/hammerjs/hammer.min.js',
    'node_modules/syn/dist/global/syn.js',
    'browser/noflo-ui.min.js',
    'spec/utils/*.js',
    'spec/*.js',
    'spec/browser/*.js',
    {
      pattern: 'spec/mockruntime.html',
      included: false,
      served: true,
      watched: true,
    },
    {
      pattern: 'index.html',
      included: false,
      served: true,
      watched: true,
    },
  ];
  const serveExternals = externals.slice(0);
  serveExternals.push('themes/*.css');
  serveExternals.push('css/*.css');
  serveExternals.forEach((pattern) => {
    if (files.indexOf(pattern) !== -1) {
      return;
    }
    files.push({
      pattern,
      included: false,
      served: true,
      watched: false,
    });
  });

  const configuration = {
    basePath: '',
    frameworks: ['mocha', 'chai'],
    files,
    exclude: [],
    preprocessors: {},
    reporters: ['mocha'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['ChromeHeadless'],
    customLaunchers: {
      ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox'],
      },
    },
    singleRun: true,
    concurrency: Infinity
  };

  if (process.env.TRAVIS) {
    configuration.browsers = ['ChromeHeadlessNoSandbox'];
  }

  config.set(configuration);
}
<|endoftext|>


<commit_before>/**
A plugin for updating the active element of a list based on the scroll position
of a certain node. Plug it into a scrollable node (usually the body) and set the
`target` attribute to the list.

@class Plugin.ScrollSpy
@constructor
@extends Plugin.ScrollInfo
@param {Object} [config] Object literal containing configuration options
**/
function ScrollSpy() {
    ScrollSpy.superclass.constructor.apply(this, arguments);
}
Y.extend(ScrollSpy, Y.Plugin.ScrollInfo, {
    initializer: function () {
        this.on('scroll', this.refresh, this);
        this.refresh();
    },
    /**
    Recalculates the current active node in the list and resets the active
    CSS class names.

    @method refresh
    **/
    refresh: function () {
        var links = this.get('target').all('a'),
            candidates = links.getAttribute('href'),
            ids = [], i = 0, length = candidates.length,
            topNode,
            activeClass = this.get('activeClass');

        for (; i < length; i++) {
            if (candidates[i].charAt(0) === '#') {
                ids.push(candidates[i]);
            }
        }

        topNode = this.getOnscreenNodes(ids.join(',')).shift();

        if (topNode) {
            topNode = '#' + topNode.get('id');
            links.batch(function (link) {
                var node = link.get('parentNode');
                node.toggleClass(activeClass, link.getAttribute('href') === topNode);
            });
        }
    }
}, {
    /**
    Plugin namespace.

    @property NS
    @type String
    @default 'scrollspy'
    @static
    **/
    NS: 'scrollspy',
    ATTRS: {
        /**
        Number of milliseconds by which ScrollSpy throttles the scroll event.
        Alternatively, provide a `data-delay` attribute in your plugged node.

        @attribute {Number} scrollDelay
        @default 15
        @initOnly
        **/
        scrollDelay: {
            valueFn: function () {
                return parseInt(this.get('host').getData('delay'), 10) || 15;
            },
            initOnly: true
        },
        /**
        The class to apply to the active item in the list.

        @attribute {String} activeClass
        @default 'yui3-menu-active'
        **/
        activeClass: {
            value: Y.ClassNameManager.getClassName('menu', 'active')
        },
        /**
        The target list. Usually a list with a `yui3-menu` class.
        Alternatively, provide a `data-target` attribute in your plugged node.

        @attribute {String|Node} target Node or selector.
        @initOnly
        **/
        target: {
            setter: Y.one,
            valueFn: function () {
                return this.get('host').getData('target');
            },
            initOnly: true
        }
    }
});

Y.Plugin.ScrollSpy = ScrollSpy;
<commit_msg>Fix target attribute name<commit_after>/**
A plugin for updating the active element of a list based on the scroll position
of a certain node. Plug it into a scrollable node (usually the body) and set the
`target` attribute to the list.

@class Plugin.ScrollSpy
@constructor
@extends Plugin.ScrollInfo
@param {Object} [config] Object literal containing configuration options
**/
function ScrollSpy() {
    ScrollSpy.superclass.constructor.apply(this, arguments);
}
Y.extend(ScrollSpy, Y.Plugin.ScrollInfo, {
    initializer: function () {
        this.on('scroll', this.refresh, this);
        this.refresh();
    },
    /**
    Recalculates the current active node in the list and resets the active
    CSS class names.

    @method refresh
    **/
    refresh: function () {
        var links = this.get('target').all('a'),
            candidates = links.getAttribute('href'),
            ids = [], i = 0, length = candidates.length,
            topNode,
            activeClass = this.get('activeClass');

        for (; i < length; i++) {
            if (candidates[i].charAt(0) === '#') {
                ids.push(candidates[i]);
            }
        }

        topNode = this.getOnscreenNodes(ids.join(',')).shift();

        if (topNode) {
            topNode = '#' + topNode.get('id');
            links.batch(function (link) {
                var node = link.get('parentNode');
                node.toggleClass(activeClass, link.getAttribute('href') === topNode);
            });
        }
    }
}, {
    /**
    Plugin namespace.

    @property NS
    @type String
    @default 'scrollspy'
    @static
    **/
    NS: 'scrollspy',
    ATTRS: {
        /**
        Number of milliseconds by which ScrollSpy throttles the scroll event.
        Alternatively, provide a `data-delay` attribute in your plugged node.

        @attribute {Number} scrollDelay
        @default 15
        @initOnly
        **/
        scrollDelay: {
            valueFn: function () {
                return parseInt(this.get('host').getData('delay'), 10) || 15;
            },
            initOnly: true
        },
        /**
        The class to apply to the active item in the list.

        @attribute {String} activeClass
        @default 'yui3-menu-active'
        **/
        activeClass: {
            value: Y.ClassNameManager.getClassName('menu', 'active')
        },
        /**
        The target list. Usually a list with a `yui3-menu` class.
        Alternatively, provide a `data-target` attribute in your plugged node.

        @attribute {String|Node} target
        @initOnly
        **/
        target: {
            setter: Y.one,
            valueFn: function () {
                return this.get('host').getData('target');
            },
            initOnly: true
        }
    }
});

Y.Plugin.ScrollSpy = ScrollSpy;
<|endoftext|>