<commit_before>'use strict';
import React, { PropTypes as T } from 'react';
import c from 'classnames';
import _ from 'lodash';
import { hashHistory } from 'react-router';

import { getLanguage } from '../../utils/i18n';

import { Modal, ModalHeader, ModalBody, ModalFooter } from '../modal';

const ProjectFormModal = React.createClass({

  propTypes: {
    revealed: T.bool,
    onCloseClick: T.func,

    projectForm: T.object,
    saveProject: T.func,

    // Only available when editing.
    editing: T.bool,
    projectData: T.object
  },

  getInitialState: function () {
    return {
      errors: {
        name: null
      },
      data: {
        name: _.get(this.props.projectData, 'name', ''),
        description: _.get(this.props.projectData, 'description', '') || ''
      }
    };
  },

  componentWillReceiveProps: function (nextProps) {
    if (this.props.projectForm.processing && !nextProps.projectForm.processing && !nextProps.projectForm.error) {
      if (!this.props.editing) {
        hashHistory.push(`${getLanguage()}/projects/${nextProps.projectForm.data.id}/setup`);
      } else {
        this.onClose();
      }
    }
  },

  onClose: function () {
    this.setState(this.getInitialState());
    this.props.onCloseClick();
  },

  checkErrors: function () {
    let control = true;
    let errors = this.getInitialState().errors;

    if (this.state.data.name === '') {
      errors.name = true;
      control = false;
    }

    this.setState({errors});
    return control;
  },

  onSubmit: function () {
    if (this.checkErrors()) {
      var payload = {
        name: this.state.data.name,
        description: this.state.data.description || null
      };

      if (this.props.editing) {
        this.props.saveProject(this.props.projectData.id, payload);
      } else {
        // On create we only want to send properties that were filled in.
        payload = _.pickBy(payload, v => v !== null);
        this.props.saveProject(payload);
      }
    }
  },

  onFieldChange: function (field, e) {
    let data = Object.assign({}, this.state.data, {[field]: e.target.value});
    this.setState({data});
  },

  renderError: function () {
    let error = this.props.projectForm.error;

    if (!error) {
      return;
    }

    if (error.statusCode === 409) {
      return
The name is already in use.
;
    } else {
      return
{error.message || error.error}
;
    }
  },

  render: function () {
    let processing = this.props.projectForm.processing;

    return (
      <Modal
        id='modal-project-metadata'
        className='modal--small'
        onCloseClick={this.onClose}
        revealed={this.props.revealed} >

        <ModalHeader>

{this.props.editing ? 'Edit project metadata' : 'Create new project'}

{this.props.editing ? 'Edit the attributes of your project.' : 'Name and describe your new project.'}

        </ModalHeader>
        <ModalBody>

        {processing ?
Processing...
 : null}

        {this.renderError()}

          <form className={c({'disable': processing})}>

              Project name *
              <input type='text' className='form__control form__control--medium' id='project-name' name='project-name' placeholder='Untitled project' value={this.state.data.name} onChange={this.onFieldChange.bind(null, 'name')} />

                {this.state.errors.name ?
A name is required.
 : null }

Keep it short and sweet.

              Description
              <textarea ref='description' className='form__control' id='project-desc' rows='2' placeholder='Say something about this project' value={this.state.data.description} onChange={this.onFieldChange.bind(null, 'description')}></textarea>

          </form>
        </ModalBody>
        <ModalFooter>
          <button className='mfa-xmark' type='button' onClick={this.onClose}>Cancel</button>
          <button className='mfa-tick' type='submit' onClick={this.onSubmit}>Save</button>
        </ModalFooter>
      </Modal>
    );
  }
});

export default ProjectFormModal;
<commit_msg>Fix project form modal data.<commit_after>'use strict';
import React, { PropTypes as T } from 'react';
import c from 'classnames';
import _ from 'lodash';
import { hashHistory } from 'react-router';

import { getLanguage } from '../../utils/i18n';

import { Modal, ModalHeader, ModalBody, ModalFooter } from '../modal';

const ProjectFormModal = React.createClass({

  propTypes: {
    revealed: T.bool,
    onCloseClick: T.func,

    projectForm: T.object,
    saveProject: T.func,

    // Only available when editing.
    editing: T.bool,
    projectData: T.object
  },

  getInitialState: function () {
    return {
      errors: {
        name: null
      },
      data: {
        name: '',
        description: ''
      }
    };
  },

  componentWillReceiveProps: function (nextProps) {
    if (this.props.projectForm.action === 'edit' &&
        this.props.projectForm.processing &&
        !nextProps.projectForm.processing &&
        !nextProps.projectForm.error) {
      if (!this.props.editing) {
        hashHistory.push(`${getLanguage()}/projects/${nextProps.projectForm.data.id}/setup`);
      } else {
        this.onClose();
      }
      return;
    }

    if (!this.props.revealed && nextProps.revealed) {
      // Modal was revealed. Be sure the data is correct.
      this.setState({data: {
        name: _.get(nextProps.projectData, 'name', ''),
        description: _.get(nextProps.projectData, 'description', '') || ''
      }});
    }
  },

  onClose: function () {
    this.setState(this.getInitialState());
    this.props.onCloseClick();
  },

  checkErrors: function () {
    let control = true;
    let errors = this.getInitialState().errors;

    if (this.state.data.name === '') {
      errors.name = true;
      control = false;
    }

    this.setState({errors});
    return control;
  },

  onSubmit: function () {
    if (this.checkErrors()) {
      var payload = {
        name: this.state.data.name,
        description: this.state.data.description || null
      };

      if (this.props.editing) {
        this.props.saveProject(this.props.projectData.id, payload);
      } else {
        // On create we only want to send properties that were filled in.
        payload = _.pickBy(payload, v => v !== null);
        this.props.saveProject(payload);
      }
    }
  },

  onFieldChange: function (field, e) {
    let data = Object.assign({}, this.state.data, {[field]: e.target.value});
    this.setState({data});
  },

  renderError: function () {
    let error = this.props.projectForm.error;

    if (!error) {
      return;
    }

    if (error.statusCode === 409) {
      return
The name is already in use.
;
    } else {
      return
{error.message || error.error}
;
    }
  },

  render: function () {
    let processing = this.props.projectForm.processing;

    return (
      <Modal
        id='modal-project-metadata'
        className='modal--small'
        onCloseClick={this.onClose}
        revealed={this.props.revealed} >

        <ModalHeader>

{this.props.editing ? 'Edit project metadata' : 'Create new project'}

{this.props.editing ? 'Edit the attributes of your project.' : 'Name and describe your new project.'}

        </ModalHeader>
        <ModalBody>

        {processing ?
Processing...
 : null}

        {this.renderError()}

          <form className={c({'disable': processing})}>

              Project name *
              <input type='text' className='form__control form__control--medium' id='project-name' name='project-name' placeholder='Untitled project' value={this.state.data.name} onChange={this.onFieldChange.bind(null, 'name')} />

                {this.state.errors.name ?
A name is required.
 : null }

Keep it short and sweet.

              Description
              <textarea ref='description' className='form__control' id='project-desc' rows='2' placeholder='Say something about this project' value={this.state.data.description} onChange={this.onFieldChange.bind(null, 'description')}></textarea>

          </form>
        </ModalBody>
        <ModalFooter>
          <button className='mfa-xmark' type='button' onClick={this.onClose}>Cancel</button>
          <button className='mfa-tick' type='submit' onClick={this.onSubmit}>Save</button>
        </ModalFooter>
      </Modal>
    );
  }
});

export default ProjectFormModal;
<|endoftext|>


<commit_before>import _ from 'lodash'
import path from 'path'
import { defaultHandlers, parse } from 'react-docgen'
import fs from 'fs'

import { parseDefaultValue, parseDocblock, parserCustomHandler, parseType } from './'

const getComponentInfo = (filepath) => {
  const absPath = path.resolve(process.cwd(), filepath)

  const contents = fs.readFileSync(absPath).toString()
  const dir = path.dirname(absPath)
  const dirname = path.basename(dir)
  const filename = path.basename(absPath)
  const filenameWithoutExt = path.basename(absPath, path.extname(absPath))

  // singular form of the component's ../../ directory
  // "element" for "src/elements/Button/Button.js"
  const componentType = path.basename(path.dirname(dir)).replace(/s$/, '')

  // start with react-docgen info
  const info = parse(contents, null, [...defaultHandlers, parserCustomHandler])

  // remove keys we don't use
  delete info.methods

  // add exported Component info
  const Component = require(absPath).default
  info.constructorName = _.get(Component, 'prototype.constructor.name', null)

  // add component type
  info.type = componentType

  // add parent/child info
  info.isParent = filenameWithoutExt === dirname
  info.isChild = !info.isParent
  info.parentDisplayName = info.isParent ? null : dirname
  // "Field" for "FormField" since it is accessed as "Form.Field" in the API
  info.subcomponentName = info.isParent
    ? null
    : info.displayName.replace(info.parentDisplayName, '')

  info.subcomponents = info.isParent
    ? fs
      .readdirSync(dir)
      .filter(file => /^(?!index).*\.js$/.test(file))
      .filter(file => dirname !== path.basename(file, path.extname(file)))
      .map(file => path.basename(file, path.extname(file)))
    : null

  // where this component should be exported in the api
  info.apiPath = info.isChild
    ? `${info.parentDisplayName}.${info.subcomponentName}`
    : info.displayName

  // class name for the component
  // example, the "button" in class="ui button"
  // name of the component, sub component, or plural parent for sub component groups
  info.componentClassName = (info.isChild
    ? info.subcomponentName.replace(/Group$/, `${info.parentDisplayName}s`)
    : info.displayName
  ).toLowerCase()

  // replace the component.description string with a parsed docblock object
  info.docblock = parseDocblock(info.description)
  delete info.description

  // file and path info
  info.repoPath = absPath
    .replace(`${process.cwd()}${path.sep}`, '')
    .replace(new RegExp(_.escapeRegExp(path.sep), 'g'), '/')
  info.filename = filename
  info.filenameWithoutExt = filenameWithoutExt

  // replace prop `description` strings with a parsed docblock object and updated `type`
  _.each(info.props, (propDef, propName) => {
    const { description, tags } = parseDocblock(propDef.description)
    const { name, value } = parseType(propName, propDef)

    info.props[propName] = {
      ...propDef,
      description,
      tags,
      value,
      defaultValue: parseDefaultValue(propDef),
      name: propName,
      type: name,
    }
  })

  // sort props
  info.props = _.sortBy(info.props, 'name')

  return info
}

export default getComponentInfo
<commit_msg>chore(getComponentInfo): handle HOC default exports (#2883)<commit_after>import _ from 'lodash'
import path from 'path'
import { defaultHandlers, parse, resolver } from 'react-docgen'
import fs from 'fs'

import { parseDefaultValue, parseDocblock, parserCustomHandler, parseType } from './'

const getComponentInfo = (filepath) => {
  const absPath = path.resolve(process.cwd(), filepath)

  const contents = fs.readFileSync(absPath).toString()
  const dir = path.dirname(absPath)
  const dirname = path.basename(dir)
  const filename = path.basename(absPath)
  const filenameWithoutExt = path.basename(absPath, path.extname(absPath))

  // singular form of the component's ../../ directory
  // "element" for "src/elements/Button/Button.js"
  const componentType = path.basename(path.dirname(dir)).replace(/s$/, '')

  // start with react-docgen info
  const components = parse(contents, resolver.findAllComponentDefinitions, [
    ...defaultHandlers,
    parserCustomHandler,
  ])
  if (!components.length) {
    throw new Error(`Could not find a component definition in "${filepath}".`)
  }
  if (components.length > 1) {
    throw new Error(
      [
        `Found more than one component definition in "${filepath}".`,
        'This is currently not supported, please ensure your module only defines a single React component.',
      ].join(' '),
    )
  }
  const info = components[0]

  // remove keys we don't use
  delete info.methods

  // add exported Component info
  const Component = require(absPath).default
  info.constructorName = _.get(Component, 'prototype.constructor.name', null)

  // add component type
  info.type = componentType

  // add parent/child info
  info.isParent = filenameWithoutExt === dirname
  info.isChild = !info.isParent
  info.parentDisplayName = info.isParent ? null : dirname
  // "Field" for "FormField" since it is accessed as "Form.Field" in the API
  info.subcomponentName = info.isParent
    ? null
    : info.displayName.replace(info.parentDisplayName, '')

  // "ListItem.js" is a subcomponent is the "List" directory
  const subcomponentRegExp = new RegExp(`^${dirname}\\w+\\.js$`)

  info.subcomponents = info.isParent
    ? fs
      .readdirSync(dir)
      .filter(file => subcomponentRegExp.test(file))
      .map(file => path.basename(file, path.extname(file)))
    : null

  // where this component should be exported in the api
  info.apiPath = info.isChild
    ? `${info.parentDisplayName}.${info.subcomponentName}`
    : info.displayName

  // class name for the component
  // example, the "button" in class="ui button"
  // name of the component, sub component, or plural parent for sub component groups
  info.componentClassName = (info.isChild
    ? info.subcomponentName.replace(/Group$/, `${info.parentDisplayName}s`)
    : info.displayName
  ).toLowerCase()

  // replace the component.description string with a parsed docblock object
  info.docblock = parseDocblock(info.description)
  delete info.description

  // file and path info
  info.repoPath = absPath
    .replace(`${process.cwd()}${path.sep}`, '')
    .replace(new RegExp(_.escapeRegExp(path.sep), 'g'), '/')
  info.filename = filename
  info.filenameWithoutExt = filenameWithoutExt

  // replace prop `description` strings with a parsed docblock object and updated `type`
  _.each(info.props, (propDef, propName) => {
    const { description, tags } = parseDocblock(propDef.description)
    const { name, value } = parseType(propName, propDef)

    info.props[propName] = {
      ...propDef,
      description,
      tags,
      value,
      defaultValue: parseDefaultValue(propDef),
      name: propName,
      type: name,
    }
  })

  // sort props
  info.props = _.sortBy(info.props, 'name')

  return info
}

export default getComponentInfo
<|endoftext|>


<commit_before>var assert = require("assert");
var path = require("path");
var Promise = require("nodegit-promise");
var local = path.join.bind(path, __dirname);

describe("Remote", function() {
  var NodeGit = require(local("../../"));
  var Repository = require(local("../../lib/repository"));
  var Remote = require(local("../../lib/remote"));

  var reposPath = local("../repos/workdir/.git");
  var url = "https://github.com/nodegit/test";
  var url2 = "https://github.com/nodegit/test2";

  function removeOrigins(repository) {
    Remote.delete(repository, "origin1");
    Remote.delete(repository, "origin2");
    Remote.delete(repository, "origin3");
  }

  before(function() {
    var test = this;

    return Repository.open(reposPath)
      .then(function(repository) {
        test.repository = repository;

        return Remote.lookup(repository, "origin");
      })
      .then(function(remote) {
        test.remote = remote;

        return removeOrigins(test.repository);
      });
  });

  after(function() {
    return removeOrigins(this.repository);
  });

  it("can load a remote", function() {
    assert.ok(this.remote instanceof Remote);
  });

  it("can read the remote url", function() {
    assert.equal( this.remote.url().replace(".git", ""), url);
  });

  it("has an empty pushurl by default", function() {
    assert.equal(this.remote.pushurl(), undefined);
  });

  it("can set a remote", function() {
    var repository = this.repository;
    var newRemote;

    return Remote.create(repository, "origin1", url)
      .then(function(remote) {
        newRemote = remote;
        return remote.setPushurl("https://google.com/");
      })
      .then(function() {
        assert(newRemote.pushurl(), "https://google.com/");
      });
  });

  it("can read the remote name", function() {
    assert.equal(this.remote.name(), "origin");
  });

  it("can create and load a new remote", function() {
    var repository = this.repository;

    return Remote.create(repository, "origin2", url)
      .then(function() {
        return Remote.lookup(repository, "origin2");
      })
      .then(function(remote) {
        assert(remote.url(), url);
      });
  });

  it("can delete a remote", function() {
    var repository = this.repository;

    return Remote.create(repository, "origin3", url)
      .then(function(remote) {
        Remote.delete(repository, "origin3");
        return Remote.lookup(repository, "origin3");
      })
      .then(Promise.reject, Promise.resolve);
  });

  it("can download from a remote", function() {
    var repo = this.repository;

    return repo.getRemote("origin")
      .then(function(remote) {
        remote.setCallbacks({
          certificateCheck: function() {
            return 1;
          }
        });

        return remote.connect(NodeGit.Enums.DIRECTION.FETCH)
        .then(function() {
          return remote.download(null);
        }).then(function() {
          return remote.disconnect();
        });
      });
  });

  it("can monitor transfer progress while downloading", function() {
    // Set a reasonable timeout here now that our repository has grown.
    this.timeout(15000);

    var repo = this.repository;
    var wasCalled = false;

    return Remote.create(repo, "test2", url2)
    .then(function() {
      return repo.getRemote("test2");
    })
    .then(function(remote) {
      remote.setCallbacks({
        certificateCheck: function() {
          return 0;
        },

        transferProgress: function() {
          wasCalled = true;
        }
      });

      return remote.fetch(null, repo.defaultSignature(), null);
    })
    .then(function() {
      assert.ok(wasCalled);
    });
  });

  it("can fetch from a remote", function() {
    return this.repository.fetch("origin", {
      credentials: function(url, userName) {
        return NodeGit.Cred.sshKeyFromAgent(userName);
      },
      certificateCheck: function() {
        return 1;
      }
    });
  });

  it("can fetch from all remotes", function() {
    // Set a reasonable timeout here for the fetchAll test
    this.timeout(15000);

    return this.repository.fetchAll({
      credentials: function(url, userName) {
        return NodeGit.Cred.sshKeyFromAgent(userName);
      },
      certificateCheck: function() {
        return 1;
      }
    });
  });

});
<commit_msg>Increase the timeout on the transfer progress test<commit_after>var assert = require("assert");
var path = require("path");
var Promise = require("nodegit-promise");
var local = path.join.bind(path, __dirname);

describe("Remote", function() {
  var NodeGit = require(local("../../"));
  var Repository = require(local("../../lib/repository"));
  var Remote = require(local("../../lib/remote"));

  var reposPath = local("../repos/workdir/.git");
  var url = "https://github.com/nodegit/test";
  var url2 = "https://github.com/nodegit/test2";

  function removeOrigins(repository) {
    Remote.delete(repository, "origin1");
    Remote.delete(repository, "origin2");
    Remote.delete(repository, "origin3");
  }

  before(function() {
    var test = this;

    return Repository.open(reposPath)
      .then(function(repository) {
        test.repository = repository;

        return Remote.lookup(repository, "origin");
      })
      .then(function(remote) {
        test.remote = remote;

        return removeOrigins(test.repository);
      });
  });

  after(function() {
    return removeOrigins(this.repository);
  });

  it("can load a remote", function() {
    assert.ok(this.remote instanceof Remote);
  });

  it("can read the remote url", function() {
    assert.equal( this.remote.url().replace(".git", ""), url);
  });

  it("has an empty pushurl by default", function() {
    assert.equal(this.remote.pushurl(), undefined);
  });

  it("can set a remote", function() {
    var repository = this.repository;
    var newRemote;

    return Remote.create(repository, "origin1", url)
      .then(function(remote) {
        newRemote = remote;
        return remote.setPushurl("https://google.com/");
      })
      .then(function() {
        assert(newRemote.pushurl(), "https://google.com/");
      });
  });

  it("can read the remote name", function() {
    assert.equal(this.remote.name(), "origin");
  });

  it("can create and load a new remote", function() {
    var repository = this.repository;

    return Remote.create(repository, "origin2", url)
      .then(function() {
        return Remote.lookup(repository, "origin2");
      })
      .then(function(remote) {
        assert(remote.url(), url);
      });
  });

  it("can delete a remote", function() {
    var repository = this.repository;

    return Remote.create(repository, "origin3", url)
      .then(function(remote) {
        Remote.delete(repository, "origin3");
        return Remote.lookup(repository, "origin3");
      })
      .then(Promise.reject, Promise.resolve);
  });

  it("can download from a remote", function() {
    var repo = this.repository;

    return repo.getRemote("origin")
      .then(function(remote) {
        remote.setCallbacks({
          certificateCheck: function() {
            return 1;
          }
        });

        return remote.connect(NodeGit.Enums.DIRECTION.FETCH)
        .then(function() {
          return remote.download(null);
        }).then(function() {
          return remote.disconnect();
        });
      });
  });

  it("can monitor transfer progress while downloading", function() {
    // Set a reasonable timeout here now that our repository has grown.
    this.timeout(30000);

    var repo = this.repository;
    var wasCalled = false;

    return Remote.create(repo, "test2", url2)
    .then(function() {
      return repo.getRemote("test2");
    })
    .then(function(remote) {
      remote.setCallbacks({
        certificateCheck: function() {
          return 0;
        },

        transferProgress: function() {
          wasCalled = true;
        }
      });

      return remote.fetch(null, repo.defaultSignature(), null);
    })
    .then(function() {
      assert.ok(wasCalled);
    });
  });

  it("can fetch from a remote", function() {
    return this.repository.fetch("origin", {
      credentials: function(url, userName) {
        return NodeGit.Cred.sshKeyFromAgent(userName);
      },
      certificateCheck: function() {
        return 1;
      }
    });
  });

  it("can fetch from all remotes", function() {
    // Set a reasonable timeout here for the fetchAll test
    this.timeout(15000);

    return this.repository.fetchAll({
      credentials: function(url, userName) {
        return NodeGit.Cred.sshKeyFromAgent(userName);
      },
      certificateCheck: function() {
        return 1;
      }
    });
  });

});
<|endoftext|>


<commit_before>suite('title-format', function() {
  test('null getFormattedTitle if datetime is missing', function() {
    const time = document.createElement('local-time')
    assert.isUndefined(time.getFormattedTitle())
  })

  test('locale-aware getFormattedTitle for datetime value', function() {
    const time = document.createElement('local-time')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.match(time.getFormattedTitle(), /\d{4}/)
  })

  test('skips setting a title attribute if already provided', function() {
    const time = document.createElement('local-time')
    time.setAttribute('title', 'does not change')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.equal(time.getAttribute('title'), 'does not change')
  })

  test('skips setting a title attribute if datetime is missing', function() {
    const time = document.createElement('local-time')
    assert.isNull(time.getAttribute('title'))
  })

  test('sets the title attribute for datetime value', function() {
    const time = document.createElement('local-time')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.match(time.getAttribute('title'), /\d{4}/)
  })

  test('set the title attribute when parsed element is upgraded', function() {
    const root = document.createElement('div')
    root.innerHTML = '<local-time datetime="1970-01-01T00:00:00.000Z"></local-time>'
    if ('CustomElements' in window) {
      window.CustomElements.upgradeSubtree(root)
    }
    assert.match(root.children[0].getAttribute('title'), /\d{4}/)
  })
})
<commit_msg>Add test for title getting updated on attribute change<commit_after>suite('title-format', function() {
  test('null getFormattedTitle if datetime is missing', function() {
    const time = document.createElement('local-time')
    assert.isUndefined(time.getFormattedTitle())
  })

  test('locale-aware getFormattedTitle for datetime value', function() {
    const time = document.createElement('local-time')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.match(time.getFormattedTitle(), /\d{4}/)
  })

  test('skips setting a title attribute if already provided', function() {
    const time = document.createElement('local-time')
    time.setAttribute('title', 'does not change')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.equal(time.getAttribute('title'), 'does not change')
  })

  test('skips setting a title attribute if datetime is missing', function() {
    const time = document.createElement('local-time')
    assert.isNull(time.getAttribute('title'))
  })

  test('sets the title attribute for datetime value', function() {
    const time = document.createElement('local-time')
    time.setAttribute('datetime', '1970-01-01T00:00:00.000Z')
    assert.match(time.getAttribute('title'), /\d{4}/)
  })

  test('update the title attribute after a datetime value change', function() {
    const time = document.createElement('local-time')
    time.setAttribute('datetime', '1970-05-01T00:00:00.000Z')
    assert.match(time.getAttribute('title'), /1970/)
    time.setAttribute('datetime', '1979-05-01T00:00:00.000Z')
    assert.match(time.getAttribute('title'), /1979/)
  })

  test('set the title attribute when parsed element is upgraded', function() {
    const root = document.createElement('div')
    root.innerHTML = '<local-time datetime="1970-01-01T00:00:00.000Z"></local-time>'
    if ('CustomElements' in window) {
      window.CustomElements.upgradeSubtree(root)
    }
    assert.match(root.children[0].getAttribute('title'), /\d{4}/)
  })
})
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import FilterControlGroup from '../FilterControlGroup';
import {
  AccordionSet,
  Accordion,
  FilterAccordionHeader,
} from '../Accordion';
import Checkbox from '../Checkbox';

// private
const FilterCheckbox = (props) => {
  const { groupName, name, checked, onChangeFilter: ocf, disabled } = props;
  const fullName = `${groupName}.${name}`;

  return (<Checkbox
    id={`clickable-filter-${fullName.replace('.', '-')}`}
    label={name}
    name={fullName}
    checked={checked}
    onChange={ocf}
    marginBottom0
    fullWidth
    disabled={disabled}
  />);
};

FilterCheckbox.propTypes = {
  groupName: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  checked: PropTypes.bool.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
};

// private
const FilterGroup = (props) => {
  const { label, groupName, names, filters, onChangeFilter: ocf, disabled } = props;

  return (
    <FilterControlGroup
      label={label}
      disabled={disabled}
    >
      {names.map((name, index) => (
        <FilterCheckbox
          key={index}
          groupName={groupName}
          name={name}
          onChangeFilter={ocf}
          checked={!!filters[`${groupName}.${name}`]}
          disabled={disabled}
        />))}
    </FilterControlGroup>
  );
};

FilterGroup.propTypes = {
  label: PropTypes.string.isRequired,
  groupName: PropTypes.string.isRequired,
  names: PropTypes.arrayOf(PropTypes.string).isRequired,
  filters: PropTypes.object.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
};

// DEPRECATED in favour of filterState
export function initialFilterState(config, filters) {
  const state = {};

  if (filters) {
    const fullNames = filters.split(',');
    const register = {};
    for (const fullName of fullNames) {
      register[fullName] = true;
    }

    for (const group of config) {
      for (const value of group.values) {
        const valueName = (typeof value === 'object') ? value.name : value;
        const fullName = `${group.name}.${valueName}`;
        if (register[fullName]) state[fullName] = true;
      }
    }
  }

  return state;
}

export function filterState(filters) {
  const state = {};

  if (filters) {
    const fullNames = filters.split(',');
    for (const fullName of fullNames) {
      state[fullName] = true;
    }
  }

  return state;
}

// Filters come in groups. Within each group, we want to OR together
// the filters that have been selected; and we want to AND together
// the output of each group. Groups where no filter has been selected
// have no effect at all. And Groups where all filters have been
// selected have no effect unless the `restrictWhenAllSelected`
// setting is on.
//
export function filters2cql(config, filters) {
  if (!filters) return undefined;

  const groups = {};
  const fullNames = filters.split(',');
  for (const fullName of fullNames) {
    const [groupName, fieldName] = fullName.split('.');
    if (groups[groupName] === undefined) groups[groupName] = [];
    groups[groupName].push(fieldName);
  }

  const conds = [];
  for (const groupName of Object.keys(groups)) {
    const group = config.filter(g => g.name === groupName)[0];
    const cqlIndex = group.cql;

    const values = groups[groupName];
    if (values.length === group.values.length && !group.restrictWhenAllSelected)
      continue;

    const mappedValues = values.map((v) => {
      // If the field is a {name,cql} object, use the CQL.
      const obj = group.values.filter(f => typeof f === 'object' && f.name === v);
      return (obj.length > 0) ? obj[0].cql : v;
    });
    let joinedValues = mappedValues.map(v => `"${v}"`).join(' or ');
    if (values.length > 1) joinedValues = `(${joinedValues})`;

    conds.push(`${cqlIndex}=${joinedValues}`);
  }

  return conds.join(' and ');
}

// Requires calling component to have a this.updateFilters method
// DEPRECATED in favour of handleFilterChange
//
export function onChangeFilter(e) {
  const filters = Object.assign({}, this.state.filters);
  filters[e.target.name] = e.target.checked;

  this.setState({ filters });
  this.updateFilters(filters);
}

// Relies on caller providing both queryParam and transitionToParams
export function handleFilterChange(e) {
  const state = filterState(this.queryParam('filters'));
  state[e.target.name] = e.target.checked;
  this.transitionToParams({ filters: Object.keys(state).filter(key => state[key]).join(',') });
  return state;
}

export function handleFilterClear(name) {
  const state = filterState(this.queryParam('filters'));

  Object.keys(state).forEach((key) => {
    if (key.startsWith(`${name}.`)) {
      state[key] = false;
    }
  });

  this.transitionToParams({ filters: Object.keys(state).filter(key => state[key]).join(',') });
  return state;
}

export function handleClearAllFilters() {
  // Access the initialFilters that were passed to the calling component. This is probably SearchAndSort.
  this.transitionToParams({ filters: this.props.initialFilters || '', query: '' });
}

const FilterGroups = (props) => {
  const { config, filters, onChangeFilter: ocf, onClearFilter } = props;
  const disableNames = props.disableNames || {};

  return (

      <AccordionSet>
        {config.map((group, index) => (
          <Accordion
            label={group.label}
            id={`${group.label}-${index}`}
            name={group.name}
            key={index}
            separator={false}
            header={FilterAccordionHeader}
            // If any of the filters start with this group's name, then we should display the 'clear all'
            // button for this accordion.
            displayClearButton={Object.keys(filters).some(filter => filter.startsWith(group.name))}
            onClearFilter={onClearFilter}
          >
            <FilterGroup
              key={index}
              label={group.label}
              groupName={group.name}
              disabled={disableNames[group.name]}
              names={group.values.map(f => ((typeof f === 'object') ? f.name : f))}
              filters={filters}
              onChangeFilter={ocf}
            />
          </Accordion>
        ))}
      </AccordionSet>

  );
};

FilterGroups.propTypes = {
  config: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      cql: PropTypes.string.isRequired,
      values: PropTypes.arrayOf(
        PropTypes.oneOfType([
          PropTypes.string,
          PropTypes.shape({
            name: PropTypes.string.isRequired,
            cql: PropTypes.string.isRequired,
          }),
        ]),
      ).isRequired,
    }),
  ).isRequired,
  filters: PropTypes.object.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disableNames: PropTypes.shape({}),
  onClearFilter: PropTypes.func.isRequired,
};

export default FilterGroups;
<commit_msg>Refactor filter group for loop to not use continue<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import FilterControlGroup from '../FilterControlGroup';
import {
  AccordionSet,
  Accordion,
  FilterAccordionHeader,
} from '../Accordion';
import Checkbox from '../Checkbox';

// private
const FilterCheckbox = (props) => {
  const { groupName, name, checked, onChangeFilter: ocf, disabled } = props;
  const fullName = `${groupName}.${name}`;

  return (<Checkbox
    id={`clickable-filter-${fullName.replace('.', '-')}`}
    label={name}
    name={fullName}
    checked={checked}
    onChange={ocf}
    marginBottom0
    fullWidth
    disabled={disabled}
  />);
};

FilterCheckbox.propTypes = {
  groupName: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  checked: PropTypes.bool.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
};

// private
const FilterGroup = (props) => {
  const { label, groupName, names, filters, onChangeFilter: ocf, disabled } = props;

  return (
    <FilterControlGroup
      label={label}
      disabled={disabled}
    >
      {names.map((name, index) => (
        <FilterCheckbox
          key={index}
          groupName={groupName}
          name={name}
          onChangeFilter={ocf}
          checked={!!filters[`${groupName}.${name}`]}
          disabled={disabled}
        />))}
    </FilterControlGroup>
  );
};

FilterGroup.propTypes = {
  label: PropTypes.string.isRequired,
  groupName: PropTypes.string.isRequired,
  names: PropTypes.arrayOf(PropTypes.string).isRequired,
  filters: PropTypes.object.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
};

// DEPRECATED in favour of filterState
export function initialFilterState(config, filters) {
  const state = {};

  if (filters) {
    const fullNames = filters.split(',');
    const register = {};
    for (const fullName of fullNames) {
      register[fullName] = true;
    }

    for (const group of config) {
      for (const value of group.values) {
        const valueName = (typeof value === 'object') ? value.name : value;
        const fullName = `${group.name}.${valueName}`;
        if (register[fullName]) state[fullName] = true;
      }
    }
  }

  return state;
}

export function filterState(filters) {
  const state = {};

  if (filters) {
    const fullNames = filters.split(',');
    for (const fullName of fullNames) {
      state[fullName] = true;
    }
  }

  return state;
}

// Filters come in groups. Within each group, we want to OR together
// the filters that have been selected; and we want to AND together
// the output of each group. Groups where no filter has been selected
// have no effect at all. And Groups where all filters have been
// selected have no effect unless the `restrictWhenAllSelected`
// setting is on.
//
export function filters2cql(config, filters) {
  if (!filters) return undefined;

  const groups = {};
  const fullNames = filters.split(',');
  for (const fullName of fullNames) {
    const [groupName, fieldName] = fullName.split('.');
    if (groups[groupName] === undefined) groups[groupName] = [];
    groups[groupName].push(fieldName);
  }

  const conds = [];
  for (const groupName of Object.keys(groups)) {
    const group = config.filter(g => g.name === groupName)[0];
    const cqlIndex = group.cql;

    const values = groups[groupName];
    if (!(values.length === group.values.length && !group.restrictWhenAllSelected)) {
      const mappedValues = values.map((v) => {
        // If the field is a {name,cql} object, use the CQL.
        const obj = group.values.filter(f => typeof f === 'object' && f.name === v);
        return (obj.length > 0) ? obj[0].cql : v;
      });
      let joinedValues = mappedValues.map(v => `"${v}"`).join(' or ');
      if (values.length > 1) joinedValues = `(${joinedValues})`;

      conds.push(`${cqlIndex}=${joinedValues}`);
    }
  }

  return conds.join(' and ');
}

// Requires calling component to have a this.updateFilters method
// DEPRECATED in favour of handleFilterChange
//
export function onChangeFilter(e) {
  const filters = Object.assign({}, this.state.filters);
  filters[e.target.name] = e.target.checked;

  this.setState({ filters });
  this.updateFilters(filters);
}

// Relies on caller providing both queryParam and transitionToParams
export function handleFilterChange(e) {
  const state = filterState(this.queryParam('filters'));
  state[e.target.name] = e.target.checked;
  this.transitionToParams({ filters: Object.keys(state).filter(key => state[key]).join(',') });
  return state;
}

export function handleFilterClear(name) {
  const state = filterState(this.queryParam('filters'));

  Object.keys(state).forEach((key) => {
    if (key.startsWith(`${name}.`)) {
      state[key] = false;
    }
  });

  this.transitionToParams({ filters: Object.keys(state).filter(key => state[key]).join(',') });
  return state;
}

export function handleClearAllFilters() {
  // Access the initialFilters that were passed to the calling component. This is probably SearchAndSort.
  this.transitionToParams({ filters: this.props.initialFilters || '', query: '' });
}

const FilterGroups = (props) => {
  const { config, filters, onChangeFilter: ocf, onClearFilter } = props;
  const disableNames = props.disableNames || {};

  return (

      <AccordionSet>
        {config.map((group, index) => (
          <Accordion
            label={group.label}
            id={`${group.label}-${index}`}
            name={group.name}
            key={index}
            separator={false}
            header={FilterAccordionHeader}
            // If any of the filters start with this group's name, then we should display the 'clear all'
            // button for this accordion.
            displayClearButton={Object.keys(filters).some(filter => filter.startsWith(group.name))}
            onClearFilter={onClearFilter}
          >
            <FilterGroup
              key={index}
              label={group.label}
              groupName={group.name}
              disabled={disableNames[group.name]}
              names={group.values.map(f => ((typeof f === 'object') ? f.name : f))}
              filters={filters}
              onChangeFilter={ocf}
            />
          </Accordion>
        ))}
      </AccordionSet>

  );
};

FilterGroups.propTypes = {
  config: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      cql: PropTypes.string.isRequired,
      values: PropTypes.arrayOf(
        PropTypes.oneOfType([
          PropTypes.string,
          PropTypes.shape({
            name: PropTypes.string.isRequired,
            cql: PropTypes.string.isRequired,
          }),
        ]),
      ).isRequired,
    }),
  ).isRequired,
  filters: PropTypes.object.isRequired,
  onChangeFilter: PropTypes.func.isRequired,
  disableNames: PropTypes.shape({}),
  onClearFilter: PropTypes.func.isRequired,
};

export default FilterGroups;
<|endoftext|>


<commit_before>var KZMBBCODEEXTENSIONS = (() => {
    //Def Const
    const _ = require('lodash')
    const DEFAULTS = {
        type: 'bootstrap',
        button: {
            classes: 'btn btn-large btn-block btn-default btn-primary'
        }
    }
    //Def Vars
    var public = {}
    public.button = {
        openTag: (params, content) => {
            if (!params) {
                return `<button class="${DEFAULTS.button.classes}">`
            }
            else {
                return this[`${DEFAULTS.type}ButtonHandler`](params)
            }
        },
        closeTag: (params, content) => {
            return '</button>'
        }
    }
    //Def Functions
    let bootstrapButtonHandler = (params) => {
        let classes = ['btn']
        params.type ? classes.push(`btn-${params.type}`) : classes.push('btn-default')
        if (params.block !== false) classes.push('btn-block')
        if (params.size) classes.push(`btn-${params.size}`)
        if (params.active) classes.push('active')
        const classstring = classes.join(' ')
        return `<button class="${classes.join(' ')}" ${params.disabled ? 'disabled="disabled"' : null}>`
    }
    public.changeDefaults = (options) => {
        return (Object.assign(DEFAULTS, options) ? true : false)
    }
    public.changeDefault = (key, option) => {
        DEFAULTS[key] = option
        return true
    }
    public.addDefaults = (options) => {
        _.forEach(options, (value, key) => {
            if (DEFAULTS[key]) throw new ReferenceError(`The option ${key} is already set, please use the changeDefault(params) method for this.`)
            DEFAULTS[key] = value
        })
    }
    return public
})

try {
    if (module) {
        module.exports = KZMBBCODEEXTENSIONS;
    }
}
catch (ReferenceError) {
    const debug = window.debug || (typeof v8debug === 'object')
    debug ? console.warn('CommonJS not supported, module not exported.') : null
}<commit_msg>fixed pr<commit_after>var KZMBBCODEEXTENSIONS = (() => {
    //Def Const
    const isBrowser = typeof window !== 'undefined';
    const _ = isBrowser ? window._ : require('lodash')
    const DEFAULTS = {
        type: 'bootstrap',
        button: {
            classes: 'btn btn-large btn-block btn-default btn-primary'
        }
    }
    //Def Vars
    var public = {}
    public.button = {
        openTag: (params, content) => {
            if (!params) {
                return `<button class="${DEFAULTS.button.classes}">`
            }
            else {
                return this[`${DEFAULTS.type}ButtonHandler`](params)
            }
        },
        closeTag: (params, content) => {
            return '</button>'
        }
    }
    //Def Functions
    let bootstrapButtonHandler = (params) => {
        let classes = ['btn']
        params.type ? classes.push(`btn-${params.type}`) : classes.push('btn-default')
        if (params.block !== false) classes.push('btn-block')
        if (params.size) classes.push(`btn-${params.size}`)
        if (params.active) classes.push('active')
        const classstring = classes.join(' ')
        return `<button class="${classes.join(' ')}" ${params.disabled ? 'disabled="disabled"' : null}>`
    }
    public.changeDefaults = (options) => {
        return (Object.assign(DEFAULTS, options) ? true : false)
    }
    public.changeDefault = (key, option) => {
        DEFAULTS[key] = option
        return true
    }
    public.addDefaults = (options) => {
        _.forEach(options, (value, key) => {
            if (DEFAULTS[key]) throw new ReferenceError(`The option ${key} is already set, please use the changeDefault(params) method for this.`)
            DEFAULTS[key] = value
        })
    }
    return public
})

try {
    if (module) {
        module.exports = KZMBBCODEEXTENSIONS;
    }
}
catch (ReferenceError) {
    const debug = window.debug || (typeof v8debug === 'object')
    debug ? console.warn('CommonJS not supported, module not exported.') : null
}<|endoftext|>


<commit_before>/**
 * Unit tests for Globals
 */

// References for Resharper
/// <reference path="../node_modules/qunitjs/qunit/qunit.js"/>
/// <reference path="../../src/firebolt.js"/>

QUnit.module('Globals');

QUnit.test('FB', function(assert) {
	assert.equal(window.FB, Firebolt, 'window.FB === Firebolt');
});

QUnit.test('$', function(assert) {
	assert.equal(window.$, Firebolt, 'window.$ === Firebolt');
});

QUnit.test('$$', function(assert) {
	assert.equal(window.$$('qunit'), document.getElementById('qunit'),
		'Acts as an alias for document.getElementById');
});

QUnit.test('$CLS', function(assert) {
	assert.equal(window.$CLS('class1'), document.getElementsByClassName('class1'),
		'Acts as an alias for document.getElementsByClassName');
});

QUnit.test('$ID', function(assert) {
	assert.equal(window.$ID, window.$$, 'Is an alias of `window.$$`.');
});

QUnit.test('$NAME', function(assert) {
	assert.equal(window.$NAME('name1'), document.getElementsByName('name1'),
		'Acts as an alias for document.getElementsByName');
});

QUnit.test('$QS', function(assert) {
	assert.equal(window.$QS('#qunit'), document.querySelector('#qunit'),
		'Acts as an alias for document.querySelector');
});

QUnit.test('$QSA', function(assert) {
	assert.deepEqual(window.$QSA('body > div'), document.querySelectorAll('body > div'),
		'Acts as an alias for document.querySelectorAll');
});

QUnit.test('$TAG', function(assert) {
	assert.equal(window.$TAG('div'), document.getElementsByTagName('div'),
		'Acts as an alias for document.getElementsByTagName');
});
<commit_msg>test: Add unit tests for the single selector - $1()<commit_after>/**
 * Unit tests for Globals
 */

// References for Resharper
/// <reference path="../node_modules/qunitjs/qunit/qunit.js"/>
/// <reference path="../../src/firebolt.js"/>

QUnit.module('Globals');

QUnit.test('FB', function(assert) {
	assert.equal(window.FB, Firebolt, 'window.FB === Firebolt');
});

QUnit.test('$', function(assert) {
	assert.equal(window.$, Firebolt, 'window.$ === Firebolt');
});

QUnit.test('$$', function(assert) {
	assert.equal(window.$$('qunit'), document.getElementById('qunit'),
		'Acts as an alias for document.getElementById');
});

QUnit.test('$1', function(assert) {
	var selectors = {
		id: '#qunit',
		nonExistantId: '#fake',
		tagName: 'div',
		nonExistantTagName: 'fake',
		className: '.class1',
		nonExistantClassName: '.fake',
		multipleClassNames: '.class1.first',
		attribute: '[src]',
		randomQuerySelector: 'body > div, script'
	};

	for (var selectorType in selectors) {
		var selector = selectors[selectorType];
		var result = window.$1(selector);
		var expected = document.querySelector(selector);

		assert.equal(result, expected, 'Correctly selects an element by ' + selectorType + '.');
	}

	var element = window.$1('
content

hmmcol
');
	assert.ok(element instanceof HTMLDivElement && element.parentNode === null,
		'Creates a single element when the first character in the string is a "<".');
});

QUnit.test('$CLS', function(assert) {
	assert.equal(window.$CLS('class1'), document.getElementsByClassName('class1'),
		'Acts as an alias for document.getElementsByClassName');
});

QUnit.test('$ID', function(assert) {
	assert.equal(window.$ID, window.$$, 'Is an alias of `window.$$`.');
});

QUnit.test('$NAME', function(assert) {
	assert.equal(window.$NAME('name1'), document.getElementsByName('name1'),
		'Acts as an alias for document.getElementsByName');
});

QUnit.test('$QS', function(assert) {
	assert.equal(window.$QS('#qunit'), document.querySelector('#qunit'),
		'Acts as an alias for document.querySelector');
});

QUnit.test('$QSA', function(assert) {
	assert.deepEqual(window.$QSA('body > div'), document.querySelectorAll('body > div'),
		'Acts as an alias for document.querySelectorAll');
});

QUnit.test('$TAG', function(assert) {
	assert.equal(window.$TAG('div'), document.getElementsByTagName('div'),
		'Acts as an alias for document.getElementsByTagName');
});
<|endoftext|>


<commit_before>// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
/* eslint object-shorthand: 0, global-require: 0 */

import expect from 'expect';
import { Application } from 'spectron';
import { getRoot, getElectronPath } from '../../build/utils';
import startFixtureServer from '../utils/server';

// WebDriver takes unicode mappings to certain keys,
// like arrow and return.
// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#post-sessionsessionidelementidvalue
const TAB = '\uE004';
const RETURN = '\uE006';

// The timeout used in all webdriver 'wait' functions.
const WEBDRIVER_TIMEOUT_IN_MS = 10000;

// This is the timeout for webdriver to start up. Default is 5000.
// Sometimes this is not long enough, and not sure what we can even
// do to make this go faster.
const WEBDRIVER_START_TIMEOUT_IN_MS = 10000;

const Driver = {

  // Timeout used by test suites: `this.timeout(Driver.TEST_TIMEOUT_IN_MS)`
  TEST_TIMEOUT_IN_MS: 60000,

  app: null,
  fixturesURL: null,
  port: null,

  start: async function() {
    expect(this.app).toBe(null, 'Attempting to start an already activated Electron instance');

    const server = startFixtureServer();

    this.app = new Application({
      path: getElectronPath(),
      args: [getRoot()],
      env: process.env,
      startTimeout: WEBDRIVER_START_TIMEOUT_IN_MS,
      waitTimeout: WEBDRIVER_TIMEOUT_IN_MS,
    });

    // app.start() can take up to 30s on Appveyor Windows builds
    await this.app.start();

    this.client = this.app.client;
    expect(this.app.isRunning()).toBe(true);
    await this.client.waitUntilWindowLoaded();

    // See http://webdriver.io/api/protocol/timeouts.html and
    // http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp.
    await this.client.timeouts('implicit', WEBDRIVER_TIMEOUT_IN_MS);

    // Wait for both BrowserWindow and first tab webview to exist
    await this.client.waitUntil(() => this.client.getWindowCount().then(count => count === 2));

    const { value: bwHandle } = await this.client.windowHandle();
    const { stop, port } = await server;

    this._bwHandle = bwHandle;
    this._stopServer = stop;
    this.port = port;
    this.fixturesURL = `http://localhost:${this.port}`;

    return this.app;
  },

  stop: async function() {
    if (this.app && this.app.isRunning()) {
      await this.app.stop();
    }

    if (this._stopServer) {
      await this._stopServer();
    }

    this.app = this.port = this.fixturesURL = this._stopServer = this._bwHandle = null;
  },

  /**
   * Fetches the redux state of the first browser window and returns
   * it as a plain JS object (de-immutable-ified).
   *
   * @return Promise<Object>
   */
  getReduxState: async function() {
    const oldHandle = await this.setTargetToBrowserWindow();

    try {
      return await this.client.execute(() =>
        JSON.stringify(require('electron').ipcRenderer.store.getState()))
        .then(res => JSON.parse(res.value));
    } finally {
      // Here's a situation where a decorator would be useful.
      if (oldHandle !== this._bwHandle) {
        await this.app.client.window(oldHandle);
      }
    }
  },

  /**
   * Polls the redux state of the first browser window
   * until the passed in predicate, which receives the state as a
   * plain JS Object, returns a truthy value, or resolves to a truthy value.
   *
   * @param {Function} predicate
   * @return Promise
   */
  waitUntilReduxState: async function(predicate) {
    let result = false;
    do {
      const state = await this.getReduxState();
      result = await predicate(state);
    } while (!result);
  },

  ipcSendToRenderer(channel, ...args) {
    this.app.electron.ipcRenderer.emit(channel, {}, ...args);
  },

  ipcSendToMain(channel, ...args) {
    this.app.electron.ipcRenderer.send(channel, ...args);
  },

  /**
   * Sets the webdriver target to the main browser window.
   */
  setTargetToBrowserWindow: async function() {
    const { value: oldHandle } = await this.app.client.windowHandle();
    await this.app.client.window(this._bwHandle);
    return oldHandle;
  },

  /**
   * Accepts a string url and iterates over the current
   * webdriver window handlers until one matches and updates
   * the context. Throws an error if not found.
   *
   * @param {String} url
   */
  setTargetByURL: async function(url) {
    const { value: handles } = await this.app.client.windowHandles();

    for (const handle of handles) {
      await this.app.client.window(handle);
      const { value: handleURL } = await this.client.url();
      if (handleURL === url) {
        return;
      }
    }

    throw new Error(`No valid webdriver handles found for ${url}`);
  },

  waitForCurrentTabLoaded: async function() {
    await this.setTargetToBrowserWindow();
    return await this.app.client
      .waitForVisible('.active-browser-page[data-page-state="loaded"]');
  },

  /**
   * Clicks the `selector` via waiting for its existence, moving and finally
   * left clicking.
   */
  click: async function(selector) {
    await this.app.client
      .waitForVisible(selector)
      .moveToObject(selector)
      .leftClick(selector);
    return this;
  },

  navigate: async function(loc) {
    await this.setTargetToBrowserWindow();
    await this.click('#browser-location-title-bar');
    await this.app.client
      .waitForVisible('#urlbar-input')
      .setValue('#urlbar-input', `${loc}${RETURN}`);
  },

  navigateBack: async function() {
    await this.setTargetToBrowserWindow();
    return await this.click('#browser-navbar-back');
  },

  navigateForward: async function() {
    await this.setTargetToBrowserWindow();
    return await this.click('#browser-navbar-forward');
  },

  getURLValue: async function() {
    const { value } = await Driver.client.execute(() =>
      document.querySelector('#urlbar-input').value
    );
    return value;
  },

  waitForURLValue: async function(val) {
    await Driver.client.waitUntil(() =>
      this.getURLValue().then(value => value === val),
    2000, 10);
  },

  /**
   * A bit hacky, but ensures we don't have the url bar focused
   */
  blur: async function() {
    await this.setTargetToBrowserWindow();

    // If our URL bar is visible, send a tab event
    if (await this.app.client.isVisible('#urlbar-input')) {
      await this.app.client.setValue('#urlbar-input', TAB);
    }
    return await this.app.client
      .waitForVisible('#browser-location-title-bar');
  },
};

export default Driver;
<commit_msg>Convert the webdriver test utils to use object shorthand notation, to satisfy eslint rules<commit_after>// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

/* eslint global-require: 0 */

import expect from 'expect';
import { Application } from 'spectron';
import { getRoot, getElectronPath } from '../../build/utils';
import startFixtureServer from '../utils/server';

// WebDriver takes unicode mappings to certain keys,
// like arrow and return.
// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#post-sessionsessionidelementidvalue
const TAB = '\uE004';
const RETURN = '\uE006';

// The timeout used in all webdriver 'wait' functions.
const WEBDRIVER_TIMEOUT_IN_MS = 10000;

// This is the timeout for webdriver to start up. Default is 5000.
// Sometimes this is not long enough, and not sure what we can even
// do to make this go faster.
const WEBDRIVER_START_TIMEOUT_IN_MS = 10000;

const Driver = {

  // Timeout used by test suites: `this.timeout(Driver.TEST_TIMEOUT_IN_MS)`
  TEST_TIMEOUT_IN_MS: 60000,

  app: null,
  fixturesURL: null,
  port: null,

  async start() {
    expect(this.app).toBe(null, 'Attempting to start an already activated Electron instance');

    const server = startFixtureServer();

    this.app = new Application({
      path: getElectronPath(),
      args: [getRoot()],
      env: process.env,
      startTimeout: WEBDRIVER_START_TIMEOUT_IN_MS,
      waitTimeout: WEBDRIVER_TIMEOUT_IN_MS,
    });

    // app.start() can take up to 30s on Appveyor Windows builds
    await this.app.start();

    this.client = this.app.client;
    expect(this.app.isRunning()).toBe(true);
    await this.client.waitUntilWindowLoaded();

    // See http://webdriver.io/api/protocol/timeouts.html and
    // http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp.
    await this.client.timeouts('implicit', WEBDRIVER_TIMEOUT_IN_MS);

    // Wait for both BrowserWindow and first tab webview to exist
    await this.client.waitUntil(() => this.client.getWindowCount().then(count => count === 2));

    const { value: bwHandle } = await this.client.windowHandle();
    const { stop, port } = await server;

    this._bwHandle = bwHandle;
    this._stopServer = stop;
    this.port = port;
    this.fixturesURL = `http://localhost:${this.port}`;

    return this.app;
  },

  async stop() {
    if (this.app && this.app.isRunning()) {
      await this.app.stop();
    }

    if (this._stopServer) {
      await this._stopServer();
    }

    this.app = this.port = this.fixturesURL = this._stopServer = this._bwHandle = null;
  },

  /**
   * Fetches the redux state of the first browser window and returns
   * it as a plain JS object (de-immutable-ified).
   *
   * @return Promise<Object>
   */
  async getReduxState() {
    const oldHandle = await this.setTargetToBrowserWindow();

    try {
      return await this.client.execute(() =>
        JSON.stringify(require('electron').ipcRenderer.store.getState()))
        .then(res => JSON.parse(res.value));
    } finally {
      // Here's a situation where a decorator would be useful.
      if (oldHandle !== this._bwHandle) {
        await this.app.client.window(oldHandle);
      }
    }
  },

  /**
   * Polls the redux state of the first browser window
   * until the passed in predicate, which receives the state as a
   * plain JS Object, returns a truthy value, or resolves to a truthy value.
   *
   * @param {Function} predicate
   * @return Promise
   */
  async waitUntilReduxState(predicate) {
    let result = false;
    do {
      const state = await this.getReduxState();
      result = await predicate(state);
    } while (!result);
  },

  ipcSendToRenderer(channel, ...args) {
    this.app.electron.ipcRenderer.emit(channel, {}, ...args);
  },

  ipcSendToMain(channel, ...args) {
    this.app.electron.ipcRenderer.send(channel, ...args);
  },

  /**
   * Sets the webdriver target to the main browser window.
   */
  async setTargetToBrowserWindow() {
    const { value: oldHandle } = await this.app.client.windowHandle();
    await this.app.client.window(this._bwHandle);
    return oldHandle;
  },

  /**
   * Accepts a string url and iterates over the current
   * webdriver window handlers until one matches and updates
   * the context. Throws an error if not found.
   *
   * @param {String} url
   */
  async setTargetByURL(url) {
    const { value: handles } = await this.app.client.windowHandles();

    for (const handle of handles) {
      await this.app.client.window(handle);
      const { value: handleURL } = await this.client.url();
      if (handleURL === url) {
        return;
      }
    }

    throw new Error(`No valid webdriver handles found for ${url}`);
  },

  async waitForCurrentTabLoaded() {
    await this.setTargetToBrowserWindow();
    return await this.app.client
      .waitForVisible('.active-browser-page[data-page-state="loaded"]');
  },

  /**
   * Clicks the `selector` via waiting for its existence, moving and finally
   * left clicking.
   */
  async click(selector) {
    await this.app.client
      .waitForVisible(selector)
      .moveToObject(selector)
      .leftClick(selector);
    return this;
  },

  async navigate(loc) {
    await this.setTargetToBrowserWindow();
    await this.click('#browser-location-title-bar');
    await this.app.client
      .waitForVisible('#urlbar-input')
      .setValue('#urlbar-input', `${loc}${RETURN}`);
  },

  async navigateBack() {
    await this.setTargetToBrowserWindow();
    return await this.click('#browser-navbar-back');
  },

  async navigateForward() {
    await this.setTargetToBrowserWindow();
    return await this.click('#browser-navbar-forward');
  },

  async getURLValue() {
    const { value } = await Driver.client.execute(() =>
      document.querySelector('#urlbar-input').value
    );
    return value;
  },

  async waitForURLValue(val) {
    await Driver.client.waitUntil(() =>
      this.getURLValue().then(value => value === val),
    2000, 10);
  },

  /**
   * A bit hacky, but ensures we don't have the url bar focused
   */
  async blur() {
    await this.setTargetToBrowserWindow();

    // If our URL bar is visible, send a tab event
    if (await this.app.client.isVisible('#urlbar-input')) {
      await this.app.client.setValue('#urlbar-input', TAB);
    }
    return await this.app.client
      .waitForVisible('#browser-location-title-bar');
  },
};

export default Driver;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

export default class ErrorBoundary extends React.PureComponent {

  static propTypes = {
    children: PropTypes.node,
  };

  state = {
    hasError: false,
    stackTrace: undefined,
    componentStack: undefined,
  }

  componentDidCatch(error, info) {
    this.setState({
      hasError: true,
      stackTrace: error.stack,
      componentStack: info && info.componentStack,
    });
  }

  handleReload(e) {
    e.preventDefault();
    window.location.reload();
  }

  render() {
    const { hasError, stackTrace, componentStack } = this.state;

    if (!hasError) return this.props.children;

    let debugInfo = '';
    if (stackTrace) {
      debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```';
    }
    if (componentStack) {
      if (debugInfo) {
        debugInfo += '\n\n\n';
      }
      debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```';
    }

    return (

<FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." />

            <FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' />

                <FormattedMessage
                  id='web_app_crash.report_issue'
                  defaultMessage='Report a bug in the {issuetracker}'
                  values={{ issuetracker: <FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /> }}
                />
                { debugInfo !== '' && (

                    <FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' />
                    <textarea
                      className='web_app_crash-stacktrace'
                      value={debugInfo}
                      rows='10'
                      readOnly
                    />

                )}

                <FormattedMessage
                  id='web_app_crash.reload_page'
                  defaultMessage='{reload} the current page'
                  values={{ reload: <FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /> }}
                />

                <FormattedMessage
                  id='web_app_crash.change_your_settings'
                  defaultMessage='Change your {settings}'
                  values={{ settings: <FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /> }}
                />

    );
  }

}
<commit_msg>Do not hardcode preferences link, pleroma doesn't have it<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { preferencesLink } from 'flavours/glitch/util/backend_links';

export default class ErrorBoundary extends React.PureComponent {

  static propTypes = {
    children: PropTypes.node,
  };

  state = {
    hasError: false,
    stackTrace: undefined,
    componentStack: undefined,
  }

  componentDidCatch(error, info) {
    this.setState({
      hasError: true,
      stackTrace: error.stack,
      componentStack: info && info.componentStack,
    });
  }

  handleReload(e) {
    e.preventDefault();
    window.location.reload();
  }

  render() {
    const { hasError, stackTrace, componentStack } = this.state;

    if (!hasError) return this.props.children;

    let debugInfo = '';
    if (stackTrace) {
      debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```';
    }
    if (componentStack) {
      if (debugInfo) {
        debugInfo += '\n\n\n';
      }
      debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```';
    }

    return (

<FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." />

            <FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' />

                <FormattedMessage
                  id='web_app_crash.report_issue'
                  defaultMessage='Report a bug in the {issuetracker}'
                  values={{ issuetracker: <FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /> }}
                />
                { debugInfo !== '' && (

                    <FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' />
                    <textarea
                      className='web_app_crash-stacktrace'
                      value={debugInfo}
                      rows='10'
                      readOnly
                    />

                )}

                <FormattedMessage
                  id='web_app_crash.reload_page'
                  defaultMessage='{reload} the current page'
                  values={{ reload: <FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /> }}
                />

              { preferencesLink !== undefined && (

                  <FormattedMessage
                    id='web_app_crash.change_your_settings'
                    defaultMessage='Change your {settings}'
                    values={{ settings: <FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /> }}
                  />

              )}

    );
  }

}
<|endoftext|>


<commit_before>import Appbase from 'appbase-js';

import valueReducer from '@appbaseio/reactivecore/lib/reducers/valueReducer';
import queryReducer from '@appbaseio/reactivecore/lib/reducers/queryReducer';
import queryOptionsReducer from '@appbaseio/reactivecore/lib/reducers/queryOptionsReducer';
import dependencyTreeReducer from '@appbaseio/reactivecore/lib/reducers/dependencyTreeReducer';
import { buildQuery, pushToAndClause } from '@appbaseio/reactivecore/lib/utils/helper';

const componentsWithOptions = [
	'ResultList',
	'ResultCard',
	'ReactiveList',
	'ReactiveMap',
	'SingleList',
	'MultiList',
	'TagCloud',
];

const resultComponents = ['ResultCard', 'ResultList', 'ReactiveList', 'ReactiveMap'];

function getValue(state, id, defaultValue) {
	return state ? state[id] : defaultValue;
}

function getQuery(component, value) {
	if (component.customQuery) {
		return component.customQuery(value, component);
	}
	return component.source.defaultQuery
		? component.source.defaultQuery(value, component)
		: {};
}

export default function initReactivesearch(componentCollection, searchState, settings) {
	return new Promise((resolve, reject) => {
		const credentials = settings.url && settings.url.trim() !== '' && !settings.credentials
			? null
			: settings.credentials;
		const config = {
			url: settings.url && settings.url.trim() !== '' ? settings.url : 'https://scalr.api.appbase.io',
			app: settings.app,
			credentials,
			type: settings.type ? settings.type : '*',
		};
		const appbaseRef = new Appbase(config);

		let components = [];
		let selectedValues = {};
		let queryList = {};
		let queryOptions = {};
		let dependencyTree = {};
		let finalQuery = [];
		let orderOfQueries = [];
		let hits = {};
		let aggregations = {};
		let state = {};

		componentCollection.forEach((component) => {
			components = [...components, component.componentId];

			let isInternalComponentPresent = false;
			const internalComponent = `${component.componentId}__internal`;
			const label = component.filterLabel || component.componentId;
			const value = getValue(searchState, label, component.defaultSelected);

			// [1] set selected values
			selectedValues = valueReducer(selectedValues, {
				type: 'SET_VALUE',
				component: component.componentId,
				label,
				value,
				showFilter: component.showFilter !== undefined ? component.showFilter : true,
				URLParams: component.URLParams || false,
			});

			// [2] set query options - main component query (valid for result components)
			if (componentsWithOptions.includes(component.type)) {
				const options = component.source.generateQueryOptions
					? component.source.generateQueryOptions(component)
					: null;

				if (options && Object.keys(options).length) {
					const { aggs, size, ...otherQueryOptions } = options;

					if (aggs && Object.keys(aggs).length) {
						isInternalComponentPresent = true;

						// query should be applied on the internal component
						// to enable feeding the data to parent component
						queryOptions = queryOptionsReducer(queryOptions, {
							type: 'SET_QUERY_OPTIONS',
							component: internalComponent,
							options: { aggs, size: size || 100 },
						});
					}

					// sort, size, from - query should be applied on the main component
					if (otherQueryOptions && Object.keys(otherQueryOptions).length) {
						let mainQueryOptions = { ...otherQueryOptions, size };
						if (isInternalComponentPresent) {
							mainQueryOptions = { ...otherQueryOptions };
						}
						if (resultComponents.includes(component.type)) {
							mainQueryOptions = {
								from: 0,
								size: component.size || 10,
								...mainQueryOptions,
							};
						}
						queryOptions = queryOptionsReducer(queryOptions, {
							type: 'SET_QUERY_OPTIONS',
							component: component.componentId,
							options: { ...mainQueryOptions },
						});
					}
				}
			}

			// [3] set dependency tree
			if (component.react || isInternalComponentPresent) {
				let { react } = component;
				if (isInternalComponentPresent) {
					react = pushToAndClause(react, internalComponent);
				}

				dependencyTree = dependencyTreeReducer(dependencyTree, {
					type: 'WATCH_COMPONENT',
					component: component.componentId,
					react,
				});
			}

			// [4] set query list
			queryList = queryReducer(queryList, {
				type: 'SET_QUERY',
				component: component.componentId,
				query: getQuery(component, value),
			});
		});

		// [5] Generate finalQuery for search
		componentCollection.forEach((component) => {
			let { queryObj, options } = buildQuery( // eslint-disable-line
				component.componentId,
				dependencyTree,
				queryList,
				queryOptions,
			);

			if (
				(queryObj && Object.keys(queryObj).length)
				|| (options && Object.keys(options).length)
				|| (queryOptions[component.componentId])
			) {
				if (!queryObj || (queryObj && !Object.keys(queryObj).length)) {
					queryObj = { match_all: {} };
				}

				orderOfQueries = [...orderOfQueries, component.componentId];

				finalQuery = [
					...finalQuery,
					{
						preference: component.componentId,
					},
					{
						query: { ...queryObj },
						...options,
						...queryOptions[component.componentId],
					},
				];
			}
		});

		state = {
			components,
			dependencyTree,
			queryList,
			queryOptions,
			selectedValues,
		};

		appbaseRef.msearch({
			type: config.type === '*' ? '' : config.type,
			body: finalQuery,
		})
			.on('data', (res) => {
				orderOfQueries.forEach((component, index) => {
					const response = res.responses[index];
					if (response.aggregations) {
						aggregations = {
							...aggregations,
							[component]: response.aggregations,
						};
					}
					hits = {
						...hits,
						[component]: {
							hits: response.hits.hits,
							total: response.hits.total,
							time: response.took,
						},
					};
				});
				state = {
					...state,
					hits,
					aggregations,
				};
				resolve(state);
			})
			.on('error', err => reject(err));
	});
}
<commit_msg>Add value parsing support for SSR<commit_after>import Appbase from 'appbase-js';

import valueReducer from '@appbaseio/reactivecore/lib/reducers/valueReducer';
import queryReducer from '@appbaseio/reactivecore/lib/reducers/queryReducer';
import queryOptionsReducer from '@appbaseio/reactivecore/lib/reducers/queryOptionsReducer';
import dependencyTreeReducer from '@appbaseio/reactivecore/lib/reducers/dependencyTreeReducer';
import { buildQuery, pushToAndClause } from '@appbaseio/reactivecore/lib/utils/helper';

const componentsWithOptions = [
	'ResultList',
	'ResultCard',
	'ReactiveList',
	'ReactiveMap',
	'SingleList',
	'MultiList',
	'TagCloud',
];

const resultComponents = ['ResultCard', 'ResultList', 'ReactiveList', 'ReactiveMap'];

function getValue(state, id, component) {
	if (component.source && component.source.parseValue) {
		const { parseValue } = component.source;
		return state
			? parseValue(state[id], component)
			: parseValue(component.defaultSelected, component);
	}
	return state ? state[id] : component.defaultSelected;
}

function getQuery(component, value) {
	if (component.customQuery) {
		return component.customQuery(value, component);
	}
	return component.source.defaultQuery
		? component.source.defaultQuery(value, component)
		: {};
}

export default function initReactivesearch(componentCollection, searchState, settings) {
	return new Promise((resolve, reject) => {
		const credentials = settings.url && settings.url.trim() !== '' && !settings.credentials
			? null
			: settings.credentials;
		const config = {
			url: settings.url && settings.url.trim() !== '' ? settings.url : 'https://scalr.api.appbase.io',
			app: settings.app,
			credentials,
			type: settings.type ? settings.type : '*',
		};
		const appbaseRef = new Appbase(config);

		let components = [];
		let selectedValues = {};
		let queryList = {};
		let queryOptions = {};
		let dependencyTree = {};
		let finalQuery = [];
		let orderOfQueries = [];
		let hits = {};
		let aggregations = {};
		let state = {};

		componentCollection.forEach((component) => {
			components = [...components, component.componentId];

			let isInternalComponentPresent = false;
			const internalComponent = `${component.componentId}__internal`;
			const label = component.filterLabel || component.componentId;
			const value = getValue(searchState, label, component);

			// [1] set selected values
			selectedValues = valueReducer(selectedValues, {
				type: 'SET_VALUE',
				component: component.componentId,
				label,
				value,
				showFilter: component.showFilter !== undefined ? component.showFilter : true,
				URLParams: component.URLParams || false,
			});

			// [2] set query options - main component query (valid for result components)
			if (componentsWithOptions.includes(component.type)) {
				const options = component.source.generateQueryOptions
					? component.source.generateQueryOptions(component)
					: null;

				if (options && Object.keys(options).length) {
					const { aggs, size, ...otherQueryOptions } = options;

					if (aggs && Object.keys(aggs).length) {
						isInternalComponentPresent = true;

						// query should be applied on the internal component
						// to enable feeding the data to parent component
						queryOptions = queryOptionsReducer(queryOptions, {
							type: 'SET_QUERY_OPTIONS',
							component: internalComponent,
							options: { aggs, size: size || 100 },
						});
					}

					// sort, size, from - query should be applied on the main component
					if (otherQueryOptions && Object.keys(otherQueryOptions).length) {
						let mainQueryOptions = { ...otherQueryOptions, size };
						if (isInternalComponentPresent) {
							mainQueryOptions = { ...otherQueryOptions };
						}
						if (resultComponents.includes(component.type)) {
							mainQueryOptions = {
								from: 0,
								size: component.size || 10,
								...mainQueryOptions,
							};
						}
						queryOptions = queryOptionsReducer(queryOptions, {
							type: 'SET_QUERY_OPTIONS',
							component: component.componentId,
							options: { ...mainQueryOptions },
						});
					}
				}
			}

			// [3] set dependency tree
			if (component.react || isInternalComponentPresent) {
				let { react } = component;
				if (isInternalComponentPresent) {
					react = pushToAndClause(react, internalComponent);
				}

				dependencyTree = dependencyTreeReducer(dependencyTree, {
					type: 'WATCH_COMPONENT',
					component: component.componentId,
					react,
				});
			}

			// [4] set query list
			queryList = queryReducer(queryList, {
				type: 'SET_QUERY',
				component: component.componentId,
				query: getQuery(component, value),
			});
		});

		// [5] Generate finalQuery for search
		componentCollection.forEach((component) => {
			let { queryObj, options } = buildQuery( // eslint-disable-line
				component.componentId,
				dependencyTree,
				queryList,
				queryOptions,
			);

			if (
				(queryObj && Object.keys(queryObj).length)
				|| (options && Object.keys(options).length)
				|| (queryOptions[component.componentId])
			) {
				if (!queryObj || (queryObj && !Object.keys(queryObj).length)) {
					queryObj = { match_all: {} };
				}

				orderOfQueries = [...orderOfQueries, component.componentId];

				finalQuery = [
					...finalQuery,
					{
						preference: component.componentId,
					},
					{
						query: { ...queryObj },
						...options,
						...queryOptions[component.componentId],
					},
				];
			}
		});

		state = {
			components,
			dependencyTree,
			queryList,
			queryOptions,
			selectedValues,
		};

		appbaseRef.msearch({
			type: config.type === '*' ? '' : config.type,
			body: finalQuery,
		})
			.on('data', (res) => {
				orderOfQueries.forEach((component, index) => {
					const response = res.responses[index];
					if (response.aggregations) {
						aggregations = {
							...aggregations,
							[component]: response.aggregations,
						};
					}
					hits = {
						...hits,
						[component]: {
							hits: response.hits.hits,
							total: response.hits.total,
							time: response.took,
						},
					};
				});
				state = {
					...state,
					hits,
					aggregations,
				};
				resolve(state);
			})
			.on('error', err => reject(err));
	});
}
<|endoftext|>