<commit_before>

(function(){

    DI = window.DI = function(me, definition, run){
        DI.add(me, definition, run);
    };

    function DiItem(name, definition) {
        this.name = name;
        this.callback = definition;
        this.resolved = false;
    };

    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
    var ARGUMENT_NAMES = /([^\s,]+)/g;

    DiItem.prototype = {

        _requirements: null,
        _instance: null,
        callback: null,
        name: null,
        resolved: false,

        getRequirements: function() {
            if (this._requirements === null) {
                var fnStr = this.callback.toString().replace(STRIP_COMMENTS, '')
                var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES)
                if (result === null) {
                    this._requirements = [];
                } else {
                    this._requirements = result;
                }
            }
            return this._requirements;
        },

        getInstance: function() {
            if (this.resolved === false) {
                this.resolved = null;
                this._instance = DI._run(this);
                this.resolved = true;
            } else if (this.resolved === null) {
                throw "Cyclic dependency detected: " + this.name;
            }
            return this._instance;
        },

        getName: function() {
            return this.name;
        }
    };

    DI.services = {};
    DI._unresolved = {};

    DI.add = function(me, definition, run) {
        if (typeof DI.services[me] === 'undefined') {
            DI.services[me] = new DiItem(me, definition);
            if (run) {
                DI._resolve(me, true);
            }
            DI._resolveUnresolved(me);
        } else {
            throw "Service already defined: "+me;
        }
    };

    DI._resolve = function(serviceName, resolveUnresolved) {
        var service = DI._get(serviceName);
        if (DI._isResolvable(service, resolveUnresolved)) {
            return service.getInstance();
        }
    };

    DI._resolveUnresolved = function(newServiceName) {
        if (typeof DI._unresolved[newServiceName] !== 'undefined') {
            var unArray = DI._unresolved[newServiceName];
            var count = unArray.length;
            for (var i = 0; i < count; i++) {
                DI._resolve(unArray[i], false);
            }
            delete DI._unresolved[newServiceName];
        }
    };

    DI._run = function(service) {
        var requirements = service.getRequirements();
        var count = requirements.length,
            requirement, requiredService,
            params = [];
        for (var i = 0; i < count; i++) {
            requirement = requirements[i];
            if (typeof DI.services[requirement] === 'undefined') {
                throw "Missing service: "+requirement;
            }
            requiredService = DI.services[requirement];
            params.push(requiredService.getInstance())
        }
        return service.callback.apply(DI, params);
    };

    DI._addToUnresolvedList = function(waitingFor, serviceName) {
        var unArray;
        if (typeof DI._unresolved[waitingFor] === 'undefined') {
            unArray = DI._unresolved[waitingFor] = [];
        } else {
            unArray = DI._unresolved[waitingFor];
        }
        unArray.push(serviceName);
    }

    DI._isResolvable = function(service, addToList) {
        var requirements = service.getRequirements();
        var count = requirements.length,
            requirement,
            foundAll = true;
        for (var i = 0; i < count; i++) {
            requirement = requirements[i];
            if (typeof DI.services[requirement] === 'undefined') {
                foundAll = false;
                if (addToList) {
                    DI._addToUnresolvedList(requirement, service.getName());
                }
            }
        }
        return foundAll;
    };

    DI._get = function(serviceName) {
        if (typeof DI.services[serviceName] !== 'undefined') {
            return DI.services[serviceName];
        } else {
            throw "Service not defined: "+serviceName;
        }
    };

    DI.get = function(serviceName) {
        return DI._get(serviceName).getInstance();
    };

})();

DI("Test", function(){
    //console.log("Neco", Neco);
    return "a";
}, true);

DI("Neco", function(Test){
    return "b"
});

console.log(DI.get("Test"));
console.log(DI.get("Neco"));
<commit_msg>removed comments<commit_after>

(function(){

    DI = window.DI = function(me, definition, run){
        DI.add(me, definition, run);
    };

    function DiItem(name, definition) {
        this.name = name;
        this.callback = definition;
        this.resolved = false;
    };

    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
    var ARGUMENT_NAMES = /([^\s,]+)/g;

    DiItem.prototype = {

        _requirements: null,
        _instance: null,
        callback: null,
        name: null,
        resolved: false,

        getRequirements: function() {
            if (this._requirements === null) {
                var fnStr = this.callback.toString().replace(STRIP_COMMENTS, '')
                var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES)
                if (result === null) {
                    this._requirements = [];
                } else {
                    this._requirements = result;
                }
            }
            return this._requirements;
        },

        getInstance: function() {
            if (this.resolved === false) {
                this.resolved = null;
                this._instance = DI._run(this);
                this.resolved = true;
            } else if (this.resolved === null) {
                throw "Cyclic dependency detected: " + this.name;
            }
            return this._instance;
        },

        getName: function() {
            return this.name;
        }
    };

    DI.services = {};
    DI._unresolved = {};

    DI.add = function(me, definition, run) {
        if (typeof DI.services[me] === 'undefined') {
            DI.services[me] = new DiItem(me, definition);
            if (run) {
                DI._resolve(me, true);
            }
            DI._resolveUnresolved(me);
        } else {
            throw "Service already defined: "+me;
        }
    };

    DI._resolve = function(serviceName, resolveUnresolved) {
        var service = DI._get(serviceName);
        if (DI._isResolvable(service, resolveUnresolved)) {
            return service.getInstance();
        }
    };

    DI._resolveUnresolved = function(newServiceName) {
        if (typeof DI._unresolved[newServiceName] !== 'undefined') {
            var unArray = DI._unresolved[newServiceName];
            var count = unArray.length;
            for (var i = 0; i < count; i++) {
                DI._resolve(unArray[i], false);
            }
            delete DI._unresolved[newServiceName];
        }
    };

    DI._run = function(service) {
        var requirements = service.getRequirements();
        var count = requirements.length,
            requirement, requiredService,
            params = [];
        for (var i = 0; i < count; i++) {
            requirement = requirements[i];
            if (typeof DI.services[requirement] === 'undefined') {
                throw "Missing service: "+requirement;
            }
            requiredService = DI.services[requirement];
            params.push(requiredService.getInstance())
        }
        return service.callback.apply(DI, params);
    };

    DI._addToUnresolvedList = function(waitingFor, serviceName) {
        var unArray;
        if (typeof DI._unresolved[waitingFor] === 'undefined') {
            unArray = DI._unresolved[waitingFor] = [];
        } else {
            unArray = DI._unresolved[waitingFor];
        }
        unArray.push(serviceName);
    }

    DI._isResolvable = function(service, addToList) {
        var requirements = service.getRequirements();
        var count = requirements.length,
            requirement,
            foundAll = true;
        for (var i = 0; i < count; i++) {
            requirement = requirements[i];
            if (typeof DI.services[requirement] === 'undefined') {
                foundAll = false;
                if (addToList) {
                    DI._addToUnresolvedList(requirement, service.getName());
                }
            }
        }
        return foundAll;
    };

    DI._get = function(serviceName) {
        if (typeof DI.services[serviceName] !== 'undefined') {
            return DI.services[serviceName];
        } else {
            throw "Service not defined: "+serviceName;
        }
    };

    DI.get = function(serviceName) {
        return DI._get(serviceName).getInstance();
    };

})();

<|endoftext|>


<commit_before>import _                  from 'lodash'
import { connect }        from 'react-redux'
import {
  deleteArticle,
  loadArticle,
  loadedArticle,
  saveArticle
}                         from '../redux/actions/article'

import ComponentBase      from '../application/ComponentBase'
import ArticleChildren    from '../components/ArticleChildren'
import Icon               from '../components/Icon'
import TabSet             from '../components/TabSet'
import TagBar             from '../components/TagBar'
import TinyMCE            from 'react-tinymce'

import editor_config      from '../config/editor'

const tinyMCE = window.tinyMCE;

class Article extends ComponentBase {
  constructor(props) {
    super(props);
    this.state = this.default_state;

    if (this.props.slug !== this.props.params.slug)
      this.props.dispatch(loadArticle(this.props.params.slug))

    this.isDirty = () => (
      !!this.state.aliases ||
      !!this.state.children ||
      !!this.state.data ||
      !!this.state.html ||
      !!this.state.tags
    );
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.params.slug !== nextProps.params.slug) {
      this.setState(this.default_state)
      this.props.dispatch(loadArticle(nextProps.params.slug))
    }
  }

  get default_state() {
    return {
      selected_tab: 0,

      aliases: null,
      children: null,
      data: null,
      html: null,
      tags: null
    }
  }

  handleDelete() {
    this.props.dispatch(deleteArticle(this.props.params.slug))
  }
  handleSave() {
    let article = {
      aliases:  this.state.aliases    || this.props.aliases,
      children: this.state.children   || this.props.children,
      data:     this.state.data       || this.props.data,
      html:     this.getCurrentHtml(),
      tags:     this.state.tags       || this.props.tags
    }

    this.props.dispatch(saveArticle(this.props.params.slug, article))
    this.setState(this.default_state)
  }

  handleHtmlChange() {
    let html = this.getCurrentHtml()
    this.setState({ html: html !== this.props.html ? html : null })
  }
  getCurrentHtml() {
    let html = '';
    switch (this.state.selected_tab) {
      case 0: /* Reading tab */
        html = this.state.html || this.props.html;
        break;
      case 1: /* TinyMCE tab */
        html = tinyMCE.activeEditor.getContent();
        break;
      case 2: /* HTML tab */
        html = this.refs.html.value;
        break;
      default:
        html = this.state.html || this.props.html;
        break;
    }
    return html;
  }
  handleTabClicked(clicked) {
    if (this.state.selected_tab == clicked.index) return;

    this.setState({
      html: clicked.index !== 0 ? this.props.html : this.state.html,
      selected_tab: clicked.index
    })
  }

  handleAliasChange(updated) {
    let aliases = _(updated).map(alias =>
      _.toLower(alias).replace(/[^\w\d/_]/g, '-').replace(/-{2,}/g, '-')
    ).sortBy().difference([this.props.params.slug]).uniq().value();
    this.setState({ aliases });
  }
  handleTagChange(updated) {
    let tags = _.sortBy(updated);
    if (_.xor(tags, this.props.tags).length)
      this.setState({ tags })
    else
      this.setState({ tags: null })
  }

  render() {
    let reader =

        <ArticleChildren articles={this.props.children} />

    return (

    { this.props.readonly
      ?
{reader}

      : <TabSet
          active={this.state.selected_tab}
          tabs={[{
            className: 'read',
            caption: <Icon name="read" size="small" />,
            contents: reader
          }, {
            className: 'edit',
            caption: <Icon name="edit" size="small" />,
            contents:
              <TinyMCE ref="tinymce"
                config={editor_config}
                content={this.state.html || this.props.html}
              />
          }, {
            className: 'html',
            caption: <Icon name="html" size="small" />,
            contents:
              <textarea ref="html"
                onChange={() => this.setState({ html: this.refs.html.value })}
                value={this.state.html || this.props.html}
              />
          }, {
            className: 'settings',
            caption: <Icon name="settings" size="small" />,
            contents:

Aliases:

Each entry below is used as an alternate name / redirect for this page.

                <TagBar
                  className="aliases-editor tag-bar"
                  tags={this.state.aliases || this.props.aliases}
                  inputProps={{
                    className: 'alias-tag tag-bar-input',
                    placeholder: 'add alias'
                  }}
                  readonly={false}
                  onChange={this.handleAliasChange}
                  onlyUnique={true}
                />

Danger

                <button className="button is-danger" onClick={this.handleDelete}>Delete this Article</button>
                Warning: This cannot be undone!

          }]}
          tabClicked={this.handleTabClicked}
        />
      }
        <TagBar
          tags={this.state.tags || this.props.tags}
          readonly={this.props.readonly}
          onChange={this.handleTagChange}
          onlyUnique={true}
        />
        {!this.isDirty() ? '' :
          <button className="save button is-success" onClick={this.handleSave}>
             <Icon name="save" size="small" />Save
           </button>
        }

    )
  }
}

export default connect(
  state => Object.assign(
    {}, state.article,
    { readonly: !state.user.privileges.includes('edit') }
  )
)(Article);
<commit_msg>:boom: Added Ctrl+S support to editor to save.<commit_after>import _                  from 'lodash'
import { connect }        from 'react-redux'
import {
  deleteArticle,
  loadArticle,
  loadedArticle,
  saveArticle
}                         from '../redux/actions/article'

import ComponentBase      from '../application/ComponentBase'
import ArticleChildren    from '../components/ArticleChildren'
import Icon               from '../components/Icon'
import TabSet             from '../components/TabSet'
import TagBar             from '../components/TagBar'
import TinyMCE            from 'react-tinymce'

import editor_config      from '../config/editor'

const tinyMCE = window.tinyMCE;

class Article extends ComponentBase {
  constructor(props) {
    super(props);
    this.state = this.default_state;

    if (this.props.slug !== this.props.params.slug)
      this.props.dispatch(loadArticle(this.props.params.slug))

    this.isDirty = () => (
      !!this.state.aliases ||
      !!this.state.children ||
      !!this.state.data ||
      !!this.state.html ||
      !!this.state.tags
    );
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.params.slug !== nextProps.params.slug) {
      this.setState(this.default_state)
      this.props.dispatch(loadArticle(nextProps.params.slug))
    }
  }

  get default_state() {
    return {
      selected_tab: 0,

      aliases: null,
      children: null,
      data: null,
      html: null,
      tags: null
    }
  }

  handleDelete() {
    this.props.dispatch(deleteArticle(this.props.params.slug))
  }
  handleSave() {
    let article = {
      aliases:  this.state.aliases    || this.props.aliases,
      children: this.state.children   || this.props.children,
      data:     this.state.data       || this.props.data,
      html:     this.getCurrentHtml(),
      tags:     this.state.tags       || this.props.tags
    }

    this.props.dispatch(saveArticle(this.props.params.slug, article))
    this.setState(this.default_state)
  }

  handleHtmlChange() {
    let html = this.getCurrentHtml()
    this.setState({ html: html !== this.props.html ? html : null })
  }
  getCurrentHtml() {
    let html = '';
    switch (this.state.selected_tab) {
      case 0: /* Reading tab */
        html = this.state.html || this.props.html;
        break;
      case 1: /* TinyMCE tab */
        html = tinyMCE.activeEditor.getContent();
        break;
      case 2: /* HTML tab */
        html = this.refs.html.value;
        break;
      default:
        html = this.state.html || this.props.html;
        break;
    }
    return html;
  }
  handleKeyDown(event) {
    if (!event.ctrlKey) return; // Only handle Ctrl+[key] events
    switch (event.key.toLowerCase()) {
      case 's':
        this.handleSave()
        break;
      default:
        return; // Default doesn't prevent anything.
    }
    event.stopPropagation()
    event.preventDefault()
    // console.log(`${event.ctrlKey ? 'ctrl+' : ''}${event.key}`)
  }
  handleTabClicked(clicked) {
    if (this.state.selected_tab == clicked.index) return;

    this.setState({
      html: clicked.index !== 0 ? this.props.html : this.state.html,
      selected_tab: clicked.index
    })
  }

  handleAliasChange(updated) {
    let aliases = _(updated).map(alias =>
      _.toLower(alias).replace(/[^\w\d/_]/g, '-').replace(/-{2,}/g, '-')
    ).sortBy().difference([this.props.params.slug]).uniq().value();
    this.setState({ aliases });
  }
  handleTagChange(updated) {
    let tags = _.sortBy(updated);
    if (_.xor(tags, this.props.tags).length)
      this.setState({ tags })
    else
      this.setState({ tags: null })
  }

  render() {
    let reader =

        <ArticleChildren articles={this.props.children} />

    return (

    { this.props.readonly
      ?
{reader}

      : <TabSet
          active={this.state.selected_tab}
          tabs={[{
            className: 'read',
            caption: <Icon name="read" size="small" />,
            contents: reader
          }, {
            className: 'edit',
            caption: <Icon name="edit" size="small" />,
            contents:
              <TinyMCE ref="tinymce"
                config={editor_config}
                content={this.state.html || this.props.html}
              />
          }, {
            className: 'html',
            caption: <Icon name="html" size="small" />,
            contents:
              <textarea ref="html"
                onChange={() => this.setState({ html: this.refs.html.value })}
                value={this.state.html || this.props.html}
              />
          }, {
            className: 'settings',
            caption: <Icon name="settings" size="small" />,
            contents:

Aliases:

Each entry below is used as an alternate name / redirect for this page.

                <TagBar
                  className="aliases-editor tag-bar"
                  tags={this.state.aliases || this.props.aliases}
                  inputProps={{
                    className: 'alias-tag tag-bar-input',
                    placeholder: 'add alias'
                  }}
                  readonly={false}
                  onChange={this.handleAliasChange}
                  onlyUnique={true}
                />

Danger

                <button className="button is-danger" onClick={this.handleDelete}>Delete this Article</button>
                Warning: This cannot be undone!

          }]}
          tabClicked={this.handleTabClicked}
        />
      }
        <TagBar
          tags={this.state.tags || this.props.tags}
          readonly={this.props.readonly}
          onChange={this.handleTagChange}
          onlyUnique={true}
        />
        {!this.isDirty() ? '' :
          <button className="save button is-success" onClick={this.handleSave}>
             <Icon name="save" size="small" />Save
           </button>
        }

    )
  }
}

export default connect(
  state => Object.assign(
    {}, state.article,
    { readonly: !state.user.privileges.includes('edit') }
  )
)(Article);
<|endoftext|>


<commit_before>// this test case is just for general minification tests that are not specific to any single setting
Tinytest.add('minifier-js - verify simple JS minifications work', (test) => {
  let result = meteorJsMinify('function add(first,second){return first + second; }\n');
  test.equal(result.code, 'function add(n,d){return n+d}');

  result = meteorJsMinify('let foo;if (typeof foo == "undefined") { console.log("undefined variable"); }\n');
  test.equal(result.code, 'let foo;void 0===foo&&console.log("undefined variable");');

  result = meteorJsMinify('let z = [1, undefined, 3];\n');
  test.equal(result.code, 'let z=[1,void 0,3];');

  result = meteorJsMinify('function a(z) { let returnValue = z == 10; return returnValue; }\n');
  test.equal(result.code, 'function a(n){let t;return 10==n}');

  result = meteorJsMinify('class Person{ constructor(name, age){ this.name = name; this.age = age; } printName(){console.log(this.name)}}\n');
  test.equal(result.code, 'class Person{constructor(s,e){this.name=s,this.age=e}printName(){console.log(this.name)}}');

});

// properties -- default(true)
Tinytest.add('minifier-js - verify properties setting', (test) => {
  let result = meteorJsMinify('const person = {};person["name"] = "brian";person["age"] = 100; function printName(person){console.log(person["name"])};printName(person);\n');
  test.equal(result.code, 'const person={};function printName(n){console.log(n.name)}person.name="brian",person.age=100,printName(person);');
});

// evaluate -- default(true)
Tinytest.add('minifier-js - verify evaluate setting', (test) => {
  let result = meteorJsMinify('let a = 10 + 20 + 30;\n');
  test.equal(result.code, 'let a=60;');
});

// this test is an evaluation, but since unsafe is false it won't get evaluated
Tinytest.add('minifier-js - verify that an unsafe evaluation will fail', (test) => {
  let result = meteorJsMinify('var a = [ "foo", "bar", "baz" ].join("");\n');
  test.equal(result.code, 'var a=["foo","bar","baz"].join("");');
});

// keep_infinity -- default(false)
Tinytest.add('minifier-js - verify keep_infinity setting', (test) => {
  let result = meteorJsMinify('let a = Infinity;\n');
  test.equal(result.code, 'let a=1/0;');
});

// sequences -- default(true)
Tinytest.add('minifier-js - verify sequences setting', (test) => {
  let result = meteorJsMinify('var name = "meteor"; var website = "www.meteor.com";var memberCount = 56;\n');
  test.equal(result.code, 'var name="meteor",website="www.meteor.com",memberCount=56;');

  result = meteorJsMinify('let name = "meteor"; let website = "www.meteor.com";let memberCount = 56;\n');
  test.equal(result.code, 'let name="meteor",website="www.meteor.com",memberCount=56;');

  result = meteorJsMinify('const name = "meteor"; const website = "www.meteor.com";const memberCount = 56;\n');
  test.equal(result.code, 'const name="meteor",website="www.meteor.com",memberCount=56;');
});

// dead_code -- default(true)
Tinytest.add('minifier-js - verify dead_code setting', (test) => {
  let result = meteorJsMinify('function f() {a();b();x = 10;return;if (x) {y();}}\n');
  test.equal(result.code, 'function f(){a(),b(),x=10}');

  result = meteorJsMinify('if (false) { console.log("hi"); }\n');
  test.equal(result.code,'0;');
});

// unsafe_proto -- default(false)
Tinytest.add('minifier-js - verify unsafe_proto setting', (test) => {
  let result = meteorJsMinify('Array.prototype.slice.call(a);\n');
  test.equal(result.code, 'Array.prototype.slice.call(a);');
});

//keep_numbers -- default(false)
Tinytest.add('minifier-js - verify keep_numbers setting', (test) => {
  let result = meteorJsMinify('let number = 1_000_000_000_000;\n');
  test.equal(result.code, 'let number=1e12;');

  result = meteorJsMinify('let number = 1000000000;\n');
  test.equal(result.code, 'let number=1e9;');

  result = meteorJsMinify('let number = 0.000_000_000_001;\n');
  test.equal(result.code, 'let number=1e-12;');

  result = meteorJsMinify('let number = 0.000000001;\n');
  test.equal(result.code,'let number=1e-9;');
});

// unused -- default(true) we set it to false
Tinytest.add('minifier-js - verify unused setting', (test) => {
  let result = meteorJsMinify('function foo(){let name = "ron";let firstName = "roger";console.log(firstName)};function bar(){let name = "ron";let firstName = "roger";console.log(firstName)};foo()\n');
  test.equal(result.code, 'function foo(){let o="ron",r="roger";console.log("roger")}function bar(){let o="ron",r="roger";console.log("roger")}foo();');
});

// drop_debugger -- default(true) we set it to false
Tinytest.add('minifier-js - verify drop_debugger setting', (test) => {
  let result = meteorJsMinify('let name = "meteor"; debugger; let age = 100;\n');
  test.equal(result.code, 'let name="meteor";debugger;let age=100;');

  result = meteorJsMinify('let name = "meteor";debugger;let age = 100;function printStatement(name) { console.log("hello QA developers " + name); debugger; };printStatement("brian");\n');
  test.equal(result.code, 'let name="meteor";debugger;let age=100;function printStatement(e){console.log("hello QA developers "+e);debugger}printStatement("brian");');
});<commit_msg>added a test case that exercises when terser fails and babel-minify is used as a fail safe<commit_after>// this test case is just for general minification tests that are not specific to any single setting
Tinytest.add('minifier-js - verify simple JS minifications work', (test) => {
  let result = meteorJsMinify('function add(first,second){return first + second; }\n');
  test.equal(result.code, 'function add(n,d){return n+d}');

  result = meteorJsMinify('let foo;if (typeof foo == "undefined") { console.log("undefined variable"); }\n');
  test.equal(result.code, 'let foo;void 0===foo&&console.log("undefined variable");');

  result = meteorJsMinify('let z = [1, undefined, 3];\n');
  test.equal(result.code, 'let z=[1,void 0,3];');

  result = meteorJsMinify('function a(z) { let returnValue = z == 10; return returnValue; }\n');
  test.equal(result.code, 'function a(n){let t;return 10==n}');

  result = meteorJsMinify('class Person{ constructor(name, age){ this.name = name; this.age = age; } printName(){console.log(this.name)}}\n');
  test.equal(result.code, 'class Person{constructor(s,e){this.name=s,this.age=e}printName(){console.log(this.name)}}');

});

// Error handling tests
Tinytest.add('minifier-js - errors are handled correctly', (test) => {
  test.throws(() => meteorJsMinify('let name = {;\n'));
});

// Unhandled Terser Syntax tests
Tinytest.add('minifier-js - syntax terser cannot handle is handled correctly by babel-minify', (test) => {

  let result = meteorJsMinify('let number = 1_000_000_000_000;\n');
  test.equal(result.code, 'let number=1e12;');
  test.equal(result.minifier, 'babel-minify');

  result = meteorJsMinify('let number = 0.000_000_000_001;\n');
  test.equal(result.code, 'let number=1e-12;');
  test.equal(result.minifier, 'babel-minify');

});

// properties -- default(true)
Tinytest.add('minifier-js - verify properties setting', (test) => {
  let result = meteorJsMinify('const person = {};person["name"] = "brian";person["age"] = 100; function printName(person){console.log(person["name"])};printName(person);\n');
  test.equal(result.code, 'const person={};function printName(n){console.log(n.name)}person.name="brian",person.age=100,printName(person);');
});

// evaluate -- default(true)
Tinytest.add('minifier-js - verify evaluate setting', (test) => {
  let result = meteorJsMinify('let a = 10 + 20 + 30;\n');
  test.equal(result.code, 'let a=60;');
});

// this test is an evaluation, but since unsafe is false it won't get evaluated
Tinytest.add('minifier-js - verify that an unsafe evaluation will fail event when evaluate is set to true', (test) => {
  let result = meteorJsMinify('var a = [ "foo", "bar", "baz" ].join("");\n');
  test.equal(result.code, 'var a=["foo","bar","baz"].join("");');
});

// keep_infinity -- default(false)
Tinytest.add('minifier-js - verify keep_infinity setting', (test) => {
  let result = meteorJsMinify('let a = Infinity;\n');
  test.equal(result.code, 'let a=1/0;');
});

// sequences -- default(true)
Tinytest.add('minifier-js - verify sequences setting', (test) => {
  let result = meteorJsMinify('var name = "meteor"; var website = "www.meteor.com";var memberCount = 56;\n');
  test.equal(result.code, 'var name="meteor",website="www.meteor.com",memberCount=56;');

  result = meteorJsMinify('let name = "meteor"; let website = "www.meteor.com";let memberCount = 56;\n');
  test.equal(result.code, 'let name="meteor",website="www.meteor.com",memberCount=56;');

  result = meteorJsMinify('const name = "meteor"; const website = "www.meteor.com";const memberCount = 56;\n');
  test.equal(result.code, 'const name="meteor",website="www.meteor.com",memberCount=56;');
});

// dead_code -- default(true)
Tinytest.add('minifier-js - verify dead_code setting', (test) => {
  let result = meteorJsMinify('function f() {a();b();x = 10;return;if (x) {y();}}\n');
  test.equal(result.code, 'function f(){a(),b(),x=10}');

  result = meteorJsMinify('if (false) { console.log("hi"); }\n');
  test.equal(result.code,'0;');
});

// unsafe_proto -- default(false)
Tinytest.add('minifier-js - verify unsafe_proto setting', (test) => {
  let result = meteorJsMinify('Array.prototype.slice.call(a);\n');
  test.equal(result.code, 'Array.prototype.slice.call(a);');
});

//keep_numbers -- default(false)
Tinytest.add('minifier-js - verify keep_numbers setting', (test) => {

  let result = meteorJsMinify('let number = 1000000000;\n');
  test.equal(result.code, 'let number=1e9;');

  result = meteorJsMinify('let number = 0.000000001;\n');
  test.equal(result.code,'let number=1e-9;');
});

// unused -- default(true) we set it to false
Tinytest.add('minifier-js - verify unused setting', (test) => {
  let result = meteorJsMinify('function foo(){let name = "ron";let firstName = "roger";console.log(firstName)};function bar(){let name = "ron";let firstName = "roger";console.log(firstName)};foo()\n');
  test.equal(result.code, 'function foo(){let o="ron",r="roger";console.log("roger")}function bar(){let o="ron",r="roger";console.log("roger")}foo();');
});

// drop_debugger -- default(true) we set it to false
Tinytest.add('minifier-js - verify drop_debugger setting', (test) => {
  let result = meteorJsMinify('let name = "meteor"; debugger; let age = 100;\n');
  test.equal(result.code, 'let name="meteor";debugger;let age=100;');

  result = meteorJsMinify('let name = "meteor";debugger;let age = 100;function printStatement(name) { console.log("hello QA developers " + name); debugger; };printStatement("brian");\n');
  test.equal(result.code, 'let name="meteor";debugger;let age=100;function printStatement(e){console.log("hello QA developers "+e);debugger}printStatement("brian");');
});<|endoftext|>


<commit_before>import path from "path";
// import url from "url"

// import pkg from "@phenomic/core/package.json"
import findCacheDir from "find-cache-dir";
// import webpack, { BannerPlugin, optimize, DefinePlugin } from "webpack"
import webpack, { BannerPlugin, optimize } from "webpack";
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";

import webpackPromise from "./webpack-promise.js";
import validate from "./validate.js";

const debug = require("debug")("phenomic:plugin:webpack");

const { UglifyJsPlugin } = optimize;
const cacheDir = findCacheDir({ name: "phenomic/webpack", create: true });
const requireSourceMapSupport = `require('${require
  .resolve("source-map-support/register")
  // windows support
  .replace(/\\/g, "/")}');`;

const wrap = JSON.stringify;

const isNotFoundError = e => e.code === "MODULE_NOT_FOUND";

const defaultExternals = [
  // we could consider node_modules as externals deps
  // and so use something like
  // /^[A-Za-z0-9-_]/
  // to not bundle all deps in the static build (for perf)
  // the problem is that if people rely on node_modules for stuff
  // like css, this breaks their build.
  //
  // @todo find a better way than a whitelist

  // Glamor integration
  /^glamor(\/.*)?/,

  // Aprodite integration
  /^aphrodite(\/.*)?/,

  "react-native-web",

  "react-helmet"
];

const getWebpackConfig = (config: PhenomicConfig) => {
  let webpackConfig;
  try {
    webpackConfig = require(path.join(config.path, "webpack.config.js"))(
      config
    );
    debug("webpack.config.js used");
  } catch (e) {
    if (!isNotFoundError(e)) {
      throw e;
    }
    debug("webpack.config.js is failing", e.toString());
    try {
      webpackConfig = require(path.join(
        config.path,
        "webpack.config.babel.js"
      ))(config);
      debug("webpack.config.babel.js used");
    } catch (e) {
      if (!isNotFoundError(e)) {
        throw e;
      }
      debug("webpack.config.babel.js is failing", e.toString());
      webpackConfig = require(path.join(__dirname, "webpack.config.js"))(
        config
      );
      debug("default webpack config used");
    }
  }
  validate(webpackConfig, config);
  debug(webpackConfig);
  return {
    ...webpackConfig,
    plugins: [
      ...(webpackConfig.plugins || []),
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": wrap(process.env.NODE_ENV)
      })
    ]
  };
};

export default function() {
  return {
    name: "@phenomic/plugin-bundler-webpack",
    addDevServerMiddlewares(config: PhenomicConfig) {
      debug("get middlewares");
      const compiler = webpack(getWebpackConfig(config));
      let assets = {};
      compiler.plugin("done", stats => {
        assets = {};
        const namedChunks = stats.compilation.namedChunks;
        Object.keys(namedChunks).forEach(chunkName => {
          const files = namedChunks[chunkName].files.filter(
            file => !file.endsWith(".hot-update.js")
          );
          if (files.length) {
            assets = {
              ...assets,
              [chunkName]: files.shift()
            };
          }
        });
      });
      return [
        (
          req: express$Request,
          res: express$Response,
          next: express$NextFunction
        ) => {
          res.locals.assets = assets;
          next();
        },
        webpackDevMiddleware(compiler, {
          stats: { chunkModules: false, assets: false }
          // @todo add this and output ourself a nice message for build status
          // noInfo: true,
          // quiet: true,
        }),
        webpackHotMiddleware(compiler, {
          reload: true
          // skip hot middleware logs if !verbose
          // log: config.verbose ? undefined : () => {},
        })
      ];
    },
    buildForPrerendering(config: PhenomicConfig) {
      debug("build for prerendering");
      const webpackConfig = getWebpackConfig(config);
      const specialConfig = {
        ...webpackConfig,
        // only keep the entry we are going to use
        entry: {
          [config.bundleName]: webpackConfig.entry[config.bundleName]
        },
        // adjust some config details to be node focused
        target: "node",
        // externals for package/relative name
        externals: [...(webpackConfig.externals || defaultExternals)],
        output: {
          publicPath: "/", // @todo make this dynamic
          path: cacheDir,
          filename: "[name].js",
          library: "app",
          libraryTarget: "commonjs2"
        },
        plugins: [
          // Remove UglifyJSPlugin from plugin stack
          ...(webpackConfig.plugins
            ? webpackConfig.plugins.filter(
                plugin => !(plugin instanceof UglifyJsPlugin)
              )
            : []),
          // sourcemaps
          new BannerPlugin({
            banner: requireSourceMapSupport,
            raw: true,
            entryOnly: false
          })
        ],
        // sourcemaps
        devtool: "#source-map"
      };
      return webpackPromise(specialConfig).then(
        () => require(path.join(cacheDir, config.bundleName)).default
      );
    },
    build(config: PhenomicConfig) {
      debug("build");
      return webpackPromise(getWebpackConfig(config)).then(
        stats => stats.toJson().assetsByChunkName
      );
    }
  };
}
<commit_msg>``@phenomic/plugin-bundler-webpack``: add ``emotion`` in the external whitelist (for static rendering)<commit_after>import path from "path";
// import url from "url"

// import pkg from "@phenomic/core/package.json"
import findCacheDir from "find-cache-dir";
// import webpack, { BannerPlugin, optimize, DefinePlugin } from "webpack"
import webpack, { BannerPlugin, optimize } from "webpack";
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";

import webpackPromise from "./webpack-promise.js";
import validate from "./validate.js";

const debug = require("debug")("phenomic:plugin:webpack");

const { UglifyJsPlugin } = optimize;
const cacheDir = findCacheDir({ name: "phenomic/webpack", create: true });
const requireSourceMapSupport = `require('${require
  .resolve("source-map-support/register")
  // windows support
  .replace(/\\/g, "/")}');`;

const wrap = JSON.stringify;

const isNotFoundError = e => e.code === "MODULE_NOT_FOUND";

const defaultExternals = [
  // we could consider node_modules as externals deps
  // and so use something like
  // /^[A-Za-z0-9-_]/
  // to not bundle all deps in the static build (for perf)
  // the problem is that if people rely on node_modules for stuff
  // like css, this breaks their build.
  //
  // @todo find a better way than a whitelist

  /^emotion(\/.*)?/,
  /^glamor(\/.*)?/,
  /^aphrodite(\/.*)?/,
  "react-native-web",
  "react-helmet"
];

const getWebpackConfig = (config: PhenomicConfig) => {
  let webpackConfig;
  try {
    webpackConfig = require(path.join(config.path, "webpack.config.js"))(
      config
    );
    debug("webpack.config.js used");
  } catch (e) {
    if (!isNotFoundError(e)) {
      throw e;
    }
    debug("webpack.config.js is failing", e.toString());
    try {
      webpackConfig = require(path.join(
        config.path,
        "webpack.config.babel.js"
      ))(config);
      debug("webpack.config.babel.js used");
    } catch (e) {
      if (!isNotFoundError(e)) {
        throw e;
      }
      debug("webpack.config.babel.js is failing", e.toString());
      webpackConfig = require(path.join(__dirname, "webpack.config.js"))(
        config
      );
      debug("default webpack config used");
    }
  }
  validate(webpackConfig, config);
  debug(webpackConfig);
  return {
    ...webpackConfig,
    plugins: [
      ...(webpackConfig.plugins || []),
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": wrap(process.env.NODE_ENV)
      })
    ]
  };
};

export default function() {
  return {
    name: "@phenomic/plugin-bundler-webpack",
    addDevServerMiddlewares(config: PhenomicConfig) {
      debug("get middlewares");
      const compiler = webpack(getWebpackConfig(config));
      let assets = {};
      compiler.plugin("done", stats => {
        assets = {};
        const namedChunks = stats.compilation.namedChunks;
        Object.keys(namedChunks).forEach(chunkName => {
          const files = namedChunks[chunkName].files.filter(
            file => !file.endsWith(".hot-update.js")
          );
          if (files.length) {
            assets = {
              ...assets,
              [chunkName]: files.shift()
            };
          }
        });
      });
      return [
        (
          req: express$Request,
          res: express$Response,
          next: express$NextFunction
        ) => {
          res.locals.assets = assets;
          next();
        },
        webpackDevMiddleware(compiler, {
          stats: { chunkModules: false, assets: false }
          // @todo add this and output ourself a nice message for build status
          // noInfo: true,
          // quiet: true,
        }),
        webpackHotMiddleware(compiler, {
          reload: true
          // skip hot middleware logs if !verbose
          // log: config.verbose ? undefined : () => {},
        })
      ];
    },
    buildForPrerendering(config: PhenomicConfig) {
      debug("build for prerendering");
      const webpackConfig = getWebpackConfig(config);
      const specialConfig = {
        ...webpackConfig,
        // only keep the entry we are going to use
        entry: {
          [config.bundleName]: webpackConfig.entry[config.bundleName]
        },
        // adjust some config details to be node focused
        target: "node",
        // externals for package/relative name
        externals: [...(webpackConfig.externals || defaultExternals)],
        output: {
          publicPath: "/", // @todo make this dynamic
          path: cacheDir,
          filename: "[name].js",
          library: "app",
          libraryTarget: "commonjs2"
        },
        plugins: [
          // Remove UglifyJSPlugin from plugin stack
          ...(webpackConfig.plugins
            ? webpackConfig.plugins.filter(
                plugin => !(plugin instanceof UglifyJsPlugin)
              )
            : []),
          // sourcemaps
          new BannerPlugin({
            banner: requireSourceMapSupport,
            raw: true,
            entryOnly: false
          })
        ],
        // sourcemaps
        devtool: "#source-map"
      };
      return webpackPromise(specialConfig).then(
        () => require(path.join(cacheDir, config.bundleName)).default
      );
    },
    build(config: PhenomicConfig) {
      debug("build");
      return webpackPromise(getWebpackConfig(config)).then(
        stats => stats.toJson().assetsByChunkName
      );
    }
  };
}
<|endoftext|>


<commit_before>import get from "lodash.get";
import gql from "graphql-tag";
import AuthorProfile from "@times-components/author-profile";
import connectGraphql from "./connect";

const query = gql`
  query ArticleQuery(
    $slug: Slug!
    $first: Int
    $skip: Int
    $imageRatio: Ratio!
  ) {
    author(slug: $slug) {
      name
      jobTitle
      biography
      image
      twitter
      url
      articles {
        count
        list(first: $first, skip: $skip) {
          content(maxCharCount: 145, markupType: "paragraph")
          id
          label
          leadAsset {
            ... on Image {
              title
              crop(ratio: $imageRatio) {
                url
              }
            }
            ... on Video {
              posterImage {
                title
                crop(ratio: $imageRatio) {
                  url
                }
              }
            }
          }
          publicationName
          publishedTime
          title
        }
      }
    }
  }
`;

const propsToVariables = ({ slug, pageSize, page, imageRatio }) => ({
  slug,
  first: pageSize,
  skip: pageSize * (page - 1),
  imageRatio
});

const transformResponse = response => {
  const author = get(response, "data.author");
  if (author) {
    return {
      data: Object.assign(
        {},
        Object.assign({}, author, {
          articles: {
            count: author.articles.count,
            list: author.articles.list.map(article => ({
              ...article,
              publishedTime: new Date(article.publishedTime)
            }))
          }
        }),
        {
          count: get(response, "data.author.articles.count"),
          pageSize: 10,
          page: 1
        }
      ),
      error: null,
      isLoading: false
    };
  }

  return {
    error: get(response, "data.error", null),
    isLoading: get(response, "data.loading", true),
    data: null
  };
};

export default connectGraphql(query, propsToVariables, transformResponse)(
  AuthorProfile
);
<commit_msg>fix: change provider query<commit_after>import get from "lodash.get";
import gql from "graphql-tag";
import AuthorProfile from "@times-components/author-profile";
import connectGraphql from "./connect";

const query = gql`
  query ArticleQuery(
    $slug: Slug!
    $first: Int
    $skip: Int
    $imageRatio: Ratio!
  ) {
    author(slug: $slug) {
      name
      jobTitle
      biography
      image
      twitter
      articles {
        count
        list(first: $first, skip: $skip) {
          content(maxCharCount: 145, markupType: "paragraph")
          id
          label
          leadAsset {
            ... on Image {
              title
              crop(ratio: $imageRatio) {
                url
              }
            }
            ... on Video {
              posterImage {
                title
                crop(ratio: $imageRatio) {
                  url
                }
              }
            }
          }
          publicationName
          publishedTime
          title
          url
        }
      }
    }
  }
`;

const propsToVariables = ({ slug, pageSize, page, imageRatio }) => ({
  slug,
  first: pageSize,
  skip: pageSize * (page - 1),
  imageRatio
});

const transformResponse = response => {
  const author = get(response, "data.author");
  if (author) {
    return {
      data: Object.assign(
        {},
        Object.assign({}, author, {
          articles: {
            count: author.articles.count,
            list: author.articles.list.map(article => ({
              ...article,
              publishedTime: new Date(article.publishedTime)
            }))
          }
        }),
        {
          count: get(response, "data.author.articles.count"),
          pageSize: 10,
          page: 1
        }
      ),
      error: null,
      isLoading: false
    };
  }

  return {
    error: get(response, "data.error", null),
    isLoading: get(response, "data.loading", true),
    data: null
  };
};

export default connectGraphql(query, propsToVariables, transformResponse)(
  AuthorProfile
);
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const areEqual = require('areEqual');
const emptyFunction = require('emptyFunction');
const invariant = require('invariant');
const warning = require('warning');

const {
  createOperationDescriptor,
  isRelayModernEnvironment,
  Network,
} = require('relay-runtime');

import type {
  CacheConfig,
  ConcreteRequest,
  GraphQLResponse,
  IEnvironment,
  PayloadError,
  RequestParameters,
  Variables,
} from 'relay-runtime';

export type DataWriteConfig = {
  query: ConcreteRequest,
  variables: Variables,
  payload: GraphQLResponse,
};

export type NetworkWriteConfig = {
  query: ConcreteRequest,
  variables?: Variables,
  payload: GraphQLResponse | (Variables => GraphQLResponse),
};

type PendingFetch = {
  request: RequestParameters,
  variables?: Variables,
  cacheConfig: ?CacheConfig,
  ident: string,
  deferred: {resolve: Function, reject: Function},
};

/**
 * The next id to return from `generateId()`.
 */
let nextId = 0;

class ReactRelayTestMocker {
  _environment: IEnvironment;
  _defaults: {[string]: $PropertyType<NetworkWriteConfig, 'payload'>} = {};
  _pendingFetches: Array<PendingFetch> = [];

  constructor(env: IEnvironment) {
    if (isRelayModernEnvironment(env)) {
      this._mockNetworkLayer(env);
    } else {
      warning(
        false,
        'Netork mocking is currently only supported in Relay Modern. ' +
          'You will not be able to resolve requests made with Relay ' +
          'Classic environments.',
      );
    }
    this._environment = env;
  }

  /**
   * Get a unique id number (as a string). Note: will wrap around after 2^32
   * calls, if your test needs that many IDs.
   *
   * @returns a unique id string
   */
  static generateId(): string {
    const toRet = nextId.toString();
    nextId++;

    return toRet;
  }

  /**
   * Create a unique identifier for a (query, variables) pair.
   * @param request: the request associated with the query
   * @param variables: the variables associated with this invocation of the
   * query
   *
   * @returns a string which can later be used to uniquely identify this query
   * in the list of pending queries
   */
  static getIdentifier(request: RequestParameters): string {
    return request.name;
  }

  /**
   * Remove variables that we don't need from the query that will make it more
   * annoying to test (e.g. client_mutation_id, actor_id)
   */
  static stripUnused(variables: Variables): Variables {
    if (variables.input) {
      const toRemove = [
        'client_mutation_id',
        'actor_id',
        'clientMutationId',
        'actorId',
      ];
      const strippedVariables = {...variables, input: {...variables.input}};
      toRemove.forEach(item => (strippedVariables.input[item] = undefined));
      return strippedVariables;
    }

    return variables;
  }

  /**
   * Replace the environment's network layer with a mocked out one to allow for
   * better testing. Mocking the network allows testing without using a mocked
   * out QueryRenderer, and will allow for easier testing of components wrapped
   * in refetch containers, for example. It also allows test writers to see how
   * their components behave under error conditions.
   */
  _mockNetworkLayer(env: IEnvironment): IEnvironment {
    const fetch = (request, variables, cacheConfig) => {
      let resolve = emptyFunction;
      let reject = emptyFunction;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });

      const strippedVars = ReactRelayTestMocker.stripUnused(variables);
      const ident = ReactRelayTestMocker.getIdentifier(request);

      // there's a default value for this query, use it
      if (this._defaults[ident]) {
        const payload = this._defaults[ident];
        return typeof payload === 'function' ? payload(strippedVars) : payload;
      }

      this._pendingFetches.push({
        ident,
        cacheConfig,
        deferred: {resolve, reject},
        request,
        variables: strippedVars,
      });
      return promise;
    };

    const isLoading = (ident: string): boolean => {
      return this._pendingFetches.some(pending => pending.ident === ident);
    };

    const resolveRawQuery = (
      toResolve: PendingFetch,
      payload: GraphQLResponse,
    ): void => {
      this._pendingFetches = this._pendingFetches.filter(
        pending => pending !== toResolve,
      );

      const {deferred} = toResolve;
      deferred.resolve(payload);
    };

    const rejectQuery = (
      toResolve: PendingFetch,
      payload: {error: PayloadError},
    ): void => {
      this._pendingFetches = this._pendingFetches.filter(
        pending => pending !== toResolve,
      );

      const {deferred} = toResolve;
      deferred.reject(payload.error);
    };

    (env: any).mock = {
      isLoading,
      rejectQuery,
      resolveRawQuery,
      fetch,
    };

    (env: any).hasMockedNetwork = true;

    (env: any).__setNet(Network.create(fetch));
    return env;
  }

  /**
   * set a default payload for a given query
   */
  setDefault(toSet: NetworkWriteConfig): void {
    const {query, payload} = toSet;
    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    this._defaults[ident] = payload;
  }

  /**
   * remove a default payload for a given query
   */
  unsetDefault(toUnset: NetworkWriteConfig): void {
    const {query} = toUnset;
    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    delete this._defaults[ident];
  }

  /**
   * Write directly to the Relay store instead of trying to resolve a query that
   * was sent via the network.
   *
   * Use this method when testing a component wrapped in a fragment container
   * (via `createFragmentContainer`). The component under test should also be
   * wrapped in a `RelayTestRenderer`.
   */
  dataWrite(config: DataWriteConfig): void {
    const {query, variables, payload} = config;

    const operationDescriptor = createOperationDescriptor(query, variables);

    invariant(
      payload.data != null && payload.errors === undefined,
      'Only `data` can be written when using `writeDirect`. You may need to ' +
        'wrap your payload in an object like `{data: payload}`.',
    );

    this._environment.commitPayload(operationDescriptor, payload.data);
  }

  /**
   * Write the data specified in config's payload to the instance's environment.
   * NOTE: callers may need to invoke `jest.runOnlyPendingTimers()` after
   * calling this function.
   *
   * @param config: an object containing the data to write and the query and
   * variables that the payload is simulating a response to
   */
  networkWrite(config: NetworkWriteConfig): void {
    invariant(
      (this._environment: any).hasMockedNetwork,
      'You cannot resolve queries without a mocked environment. Did you mean ' +
        'to use `writeDirect` instead?',
    );
    const {query, variables, payload} = config;

    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    let usedVars;

    if (variables) {
      const operationDescriptor = createOperationDescriptor(query, variables);
      usedVars = ReactRelayTestMocker.stripUnused(
        operationDescriptor.request.variables,
      );
    }

    let toResolve;
    this._pendingFetches.forEach(pending => {
      const pendingVars = pending.variables;
      if (pending.ident === ident) {
        invariant(
          !toResolve || variables,
          'Multiple queries with the same name are currently pending. You ' +
            'should pass variables to `write` so that it can determine which ' +
            'to resolve',
        );
        if (variables) {
          if (areEqual(pendingVars, usedVars)) {
            toResolve = pending;
          }
        } else {
          toResolve = pending;
        }
      }
    });

    const varMessage = usedVars
      ? ' - variables: ' + JSON.stringify(usedVars)
      : '';

    invariant(
      toResolve,
      'You are attempting to resolve a query that has not been fetched ' +
        '(%s%s).\n\tPlease ensure you passed the correct variables, or use ' +
        '`writeDirect` instead.',
      ident,
      varMessage,
    );

    const realPayload =
      typeof payload === 'function' ? payload(toResolve.variables) : payload;

    // if there are errors, reject the query
    if (realPayload.errors != null && realPayload.errors.length > 0) {
      (this._environment: any).mock.rejectQuery(toResolve, {
        error: realPayload.errors[0],
      });
    } else {
      (this._environment: any).mock.resolveRawQuery(toResolve, realPayload);
    }
  }
}

module.exports = ReactRelayTestMocker;
<commit_msg>Add static ReactRelayTestMocker.mockOutEnvironment(env) function<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const areEqual = require('areEqual');
const emptyFunction = require('emptyFunction');
const invariant = require('invariant');
const warning = require('warning');

const {
  createOperationDescriptor,
  isRelayModernEnvironment,
  Network,
} = require('relay-runtime');

import type {
  CacheConfig,
  ConcreteRequest,
  GraphQLResponse,
  IEnvironment,
  PayloadError,
  RequestParameters,
  Variables,
} from 'relay-runtime';

export type DataWriteConfig = {
  query: ConcreteRequest,
  variables: Variables,
  payload: GraphQLResponse,
};

export type NetworkWriteConfig = {
  query: ConcreteRequest,
  variables?: Variables,
  payload: GraphQLResponse | (Variables => GraphQLResponse),
};

type PendingFetch = {
  request: RequestParameters,
  variables?: Variables,
  cacheConfig: ?CacheConfig,
  ident: string,
  deferred: {resolve: Function, reject: Function},
};

/**
 * The next id to return from `generateId()`.
 */
let nextId = 0;

class ReactRelayTestMocker {
  _environment: IEnvironment;
  _defaults: {[string]: $PropertyType<NetworkWriteConfig, 'payload'>} = {};
  _pendingFetches: Array<PendingFetch> = [];

  constructor(env: IEnvironment) {
    if (isRelayModernEnvironment(env)) {
      this._mockNetworkLayer(env);
    } else {
      warning(
        false,
        'Network mocking is currently only supported in Relay Modern. ' +
          'You will not be able to resolve requests made with Relay ' +
          'Classic environments.',
      );
    }
    this._environment = env;
  }

  static mockOutEnvironment(env: IEnvironment): ReactRelayTestMocker {
    return new ReactRelayTestMocker(env);
  }

  /**
   * Get a unique id number (as a string). Note: will wrap around after 2^32
   * calls, if your test needs that many IDs.
   *
   * @returns a unique id string
   */
  static generateId(): string {
    const toRet = nextId.toString();
    nextId++;

    return toRet;
  }

  /**
   * Create a unique identifier for a (query, variables) pair.
   * @param request: the request associated with the query
   * @param variables: the variables associated with this invocation of the
   * query
   *
   * @returns a string which can later be used to uniquely identify this query
   * in the list of pending queries
   */
  static getIdentifier(request: RequestParameters): string {
    return request.name;
  }

  /**
   * Remove variables that we don't need from the query that will make it more
   * annoying to test (e.g. client_mutation_id, actor_id)
   */
  static stripUnused(variables: Variables): Variables {
    if (variables.input) {
      const toRemove = [
        'client_mutation_id',
        'actor_id',
        'clientMutationId',
        'actorId',
      ];
      const strippedVariables = {...variables, input: {...variables.input}};
      toRemove.forEach(item => (strippedVariables.input[item] = undefined));
      return strippedVariables;
    }

    return variables;
  }

  /**
   * Replace the environment's network layer with a mocked out one to allow for
   * better testing. Mocking the network allows testing without using a mocked
   * out QueryRenderer, and will allow for easier testing of components wrapped
   * in refetch containers, for example. It also allows test writers to see how
   * their components behave under error conditions.
   */
  _mockNetworkLayer(env: IEnvironment): IEnvironment {
    const fetch = (request, variables, cacheConfig) => {
      let resolve = emptyFunction;
      let reject = emptyFunction;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });

      const strippedVars = ReactRelayTestMocker.stripUnused(variables);
      const ident = ReactRelayTestMocker.getIdentifier(request);

      // there's a default value for this query, use it
      if (this._defaults[ident]) {
        const payload = this._defaults[ident];
        return typeof payload === 'function' ? payload(strippedVars) : payload;
      }

      this._pendingFetches.push({
        ident,
        cacheConfig,
        deferred: {resolve, reject},
        request,
        variables: strippedVars,
      });
      return promise;
    };

    const isLoading = (ident: string): boolean => {
      return this._pendingFetches.some(pending => pending.ident === ident);
    };

    const resolveRawQuery = (
      toResolve: PendingFetch,
      payload: GraphQLResponse,
    ): void => {
      this._pendingFetches = this._pendingFetches.filter(
        pending => pending !== toResolve,
      );

      const {deferred} = toResolve;
      deferred.resolve(payload);
    };

    const rejectQuery = (
      toResolve: PendingFetch,
      payload: {error: PayloadError},
    ): void => {
      this._pendingFetches = this._pendingFetches.filter(
        pending => pending !== toResolve,
      );

      const {deferred} = toResolve;
      deferred.reject(payload.error);
    };

    (env: any).mock = {
      isLoading,
      rejectQuery,
      resolveRawQuery,
      fetch,
    };

    (env: any).hasMockedNetwork = true;

    (env: any).__setNet(Network.create(fetch));
    return env;
  }

  /**
   * set a default payload for a given query
   */
  setDefault(toSet: NetworkWriteConfig): void {
    const {query, payload} = toSet;
    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    this._defaults[ident] = payload;
  }

  /**
   * remove a default payload for a given query
   */
  unsetDefault(toUnset: NetworkWriteConfig): void {
    const {query} = toUnset;
    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    delete this._defaults[ident];
  }

  /**
   * Write directly to the Relay store instead of trying to resolve a query that
   * was sent via the network.
   *
   * Use this method when testing a component wrapped in a fragment container
   * (via `createFragmentContainer`). The component under test should also be
   * wrapped in a `RelayTestRenderer`.
   */
  dataWrite(config: DataWriteConfig): void {
    const {query, variables, payload} = config;

    const operationDescriptor = createOperationDescriptor(query, variables);

    invariant(
      payload.data != null && payload.errors === undefined,
      'Only `data` can be written when using `writeDirect`. You may need to ' +
        'wrap your payload in an object like `{data: payload}`.',
    );

    this._environment.commitPayload(operationDescriptor, payload.data);
  }

  /**
   * Write the data specified in config's payload to the instance's environment.
   * NOTE: callers may need to invoke `jest.runOnlyPendingTimers()` after
   * calling this function.
   *
   * @param config: an object containing the data to write and the query and
   * variables that the payload is simulating a response to
   */
  networkWrite(config: NetworkWriteConfig): void {
    invariant(
      (this._environment: any).hasMockedNetwork,
      'You cannot resolve queries without a mocked environment. Did you mean ' +
        'to use `writeDirect` instead?',
    );
    const {query, variables, payload} = config;

    const ident = ReactRelayTestMocker.getIdentifier(query.params);

    let usedVars;

    if (variables) {
      const operationDescriptor = createOperationDescriptor(query, variables);
      usedVars = ReactRelayTestMocker.stripUnused(
        operationDescriptor.request.variables,
      );
    }

    let toResolve;
    this._pendingFetches.forEach(pending => {
      const pendingVars = pending.variables;
      if (pending.ident === ident) {
        invariant(
          !toResolve || variables,
          'Multiple queries with the same name are currently pending. You ' +
            'should pass variables to `write` so that it can determine which ' +
            'to resolve',
        );
        if (variables) {
          if (areEqual(pendingVars, usedVars)) {
            toResolve = pending;
          }
        } else {
          toResolve = pending;
        }
      }
    });

    const varMessage = usedVars
      ? ' - variables: ' + JSON.stringify(usedVars)
      : '';

    invariant(
      toResolve,
      'You are attempting to resolve a query that has not been fetched ' +
        '(%s%s).\n\tPlease ensure you passed the correct variables, or use ' +
        '`writeDirect` instead.',
      ident,
      varMessage,
    );

    const realPayload =
      typeof payload === 'function' ? payload(toResolve.variables) : payload;

    // if there are errors, reject the query
    if (realPayload.errors != null && realPayload.errors.length > 0) {
      (this._environment: any).mock.rejectQuery(toResolve, {
        error: realPayload.errors[0],
      });
    } else {
      (this._environment: any).mock.resolveRawQuery(toResolve, realPayload);
    }
  }
}

module.exports = ReactRelayTestMocker;
<|endoftext|>


<commit_before>const PRIORITY_EVENT_TITLE_PREFIXES = ['1.', '2.', '3.', '4.', '5.'];

function dispatchEvent(obj, event) {
    var evt = new Event(
        event,
        {
            target: obj,
            bubbles: true,
        }
    );
    obj.dispatchEvent(evt);
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertButtons(referenceNode, onclickAction, where, alternateReferenceNode) {
    var insertTarget = referenceNode;
    var buttonLabels = [
        // buttonLabel, eventTitlePrefix, buttonClassNames.
        ['1',       '1.', ['jfk-button', 'jfk-button-standard']],
        ['2',       '2.', ['jfk-button', 'jfk-button-standard']],
        ['3',       '3.', ['jfk-button', 'jfk-button-standard']],
        ['4',       '4.', ['jfk-button', 'jfk-button-standard']],
        ['5',       '5.', ['jfk-button', 'jfk-button-standard']],
        ['DONE',    '✓',  ['jfk-button', 'jfk-button-default' ]],
        ['NOPE',    '✗',  ['jfk-button', 'jfk-button-standard']],
        ['OKAY',    '▣',  ['jfk-button', 'jfk-button-standard']],
        ['AWESOME', 'ツ', ['jfk-button', 'jfk-button-standard']],
    ];
    for (var i in buttonLabels) {
        var buttonLabel = buttonLabels[i][0];
        console.log('buttonLabel:', buttonLabel);

        var eventTitlePrefix = buttonLabels[i][1];
        console.log('eventTitlePrefix:', eventTitlePrefix);

        var buttonClassNames = buttonLabels[i][2];
        console.log('buttonClassNames:', buttonClassNames);

        var button = document.createElement('button');
        button.innerText = buttonLabel;
        button.classList.add(...buttonClassNames);

        (function(myButton, myEventTitlePrefix) {
            myButton.onclick = function() {
                console.log('myButton.onclick');
                onclickAction(myButton, myEventTitlePrefix);
            };
        }(button, eventTitlePrefix));

        console.log('inserting button', button, where, insertTarget);
        if (where === 'after') {
            insertAfter(button, insertTarget);
            insertTarget = button;
        } else if (where === 'inside') {
            if (alternateReferenceNode && !PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
                alternateReferenceNode.appendChild(button);
            } else {
                referenceNode.appendChild(button);
            }
        }
    }
}

function clickButton(clickedData) {
    console.group('clickButton');
    console.log('clickedData:', clickedData);
    console.log('button clicked');

    // Today.
    var date = new Date();
    var monthName = date.toLocaleString('en-us', { 'month': 'short' });
    var todayFormattedDate = monthName + ' ' + date.getDate() + ', ' + date.getFullYear();

    // Event date.
    var startDateInput = document.querySelector('[aria-label="Start date"]');
    var endDateInput = document.querySelector('[aria-label="End date"]');
    var eventDate = startDateInput.value;
    if (endDateInput.value !== startDateInput.value) {
        eventDate += ' - ' + endDateInput.value;
    }
    console.log('eventDate:', eventDate);

    var eventTitle = document.querySelector('[aria-label="Title"]');
    var calendarEventTitle = eventTitle.value;
    console.log('before calendarEventTitle:', calendarEventTitle);

    // Remove leading number (e.g. "1. " in "1. My Calendar Event").
    calendarEventTitle = calendarEventTitle.replace(/^\d+\. /, '');
    // Remove leading ~ character.
    calendarEventTitle = calendarEventTitle.replace(/^~ /, '');

    console.log(' after calendarEventTitle:', calendarEventTitle);

    // "✓ My Event; Dec 31, 2015; event date: Jan 1, 2016"
    // "1. My Event"
    var eventTitlePrefix = clickedData['eventTitlePrefix'];
    var newCalendarEventTitle = eventTitlePrefix + ' ' + calendarEventTitle;
    if (! PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
        newCalendarEventTitle += ';' +
            ' ' + todayFormattedDate + ';' +
            ' event date: ' + eventDate;
    }
    console.log('newCalendarEventTitle:', newCalendarEventTitle);
    eventTitle.value = newCalendarEventTitle;

    dispatchEvent(eventTitle, 'input');

    var saveButton = document.querySelector('[aria-label="Save"]');
    setTimeout(function() {
        saveButton.click();
    }, 500);

    console.groupEnd();
}

var buttonClickedData;
(function(){
    var pathname = window.location.pathname;
    setInterval(function() {
        if (pathname !== window.location.pathname) {
            console.log('pathname changed');
            pathname = window.location.pathname;
            if (pathname.match(/^\/calendar\/r\/eventedit\//)) {
                console.log('on event edit page');

                if (buttonClickedData) {
                    clickButton(buttonClickedData);
                    buttonClickedData = null;
                } else {
                    // Add action buttons to calendar event edit page.
                    console.log('adding buttons');
                    var onclickAction = function(myButton, myEventTitlePrefix) {
                        buttonClickedData = {
                            'button': myButton,
                            'eventTitlePrefix': myEventTitlePrefix,
                        };
                        clickButton(buttonClickedData);
                        buttonClickedData = null;
                    };

                    var saveButton = document.querySelector('[aria-label="Save"]');

                    var buttonWrapper = document.createElement('div');
                    buttonWrapper.style.left = '350px';
                    buttonWrapper.style.position = 'absolute';
                    buttonWrapper.style.top = '65px';
                    buttonWrapper.style.width = '350px';

                    insertAfter(buttonWrapper, saveButton);
                    insertButtons(buttonWrapper, onclickAction, 'inside');
                }
            }
        }
    }, 1000);
})();

document.onclick = function(event) {
    console.group('element clicked');
    console.log('element clicked:', event.target);

    var target = event.target;
    var eventIdFound = false;
    for (var i = 0; i < 5; i++) {
        // Ignore event bubble.
        if (target.getAttribute('id') === 'xDetDlg') {
            break;
        } else if (target.getAttribute('data-eventid')) {
            eventIdFound = true;
            break;
        }
        target = target.parentElement;
    }
    console.log('target:', target);
    if (eventIdFound) {
        var eventId = target.getAttribute('data-eventid');
        console.log('event id found:', eventId);

        function modifyEventBubble() {
            var eventBubble = document.querySelector('#xDetDlg[data-eventid="' + eventId + '"]');
            console.log('eventBubble:', eventBubble);
            if (!eventBubble) {
                setTimeout(modifyEventBubble, 500);
                return;
            }

            var editEventButton = eventBubble.querySelector('[data-tooltip="Edit event"]');
            var eventEditable = editEventButton ? true : false;
            if (!eventEditable) {
                return;
            }

            var lastEventBubbleMetaItem = document.querySelector('#xDtlDlgCt > div:last-child');

            var newEventBubbleMetaItem = document.createElement('div');
            newEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(newEventBubbleMetaItem, lastEventBubbleMetaItem);

            var lastNewEventBubbleMetaItem = document.createElement('div');
            lastNewEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(lastNewEventBubbleMetaItem, newEventBubbleMetaItem);

            var onclickAction = function(myButton, myEventTitlePrefix) {
                buttonClickedData = {
                    'button': myButton,
                    'eventTitlePrefix': myEventTitlePrefix,
                };

                var editEventButton = document.querySelector('[aria-label="Edit event"]');
                console.log('editEventButton:', editEventButton);
                editEventButton.click();
            };
            insertButtons(newEventBubbleMetaItem, onclickAction, 'inside', lastNewEventBubbleMetaItem);
        }
        setTimeout(modifyEventBubble, 100);
    }
    console.groupEnd();
};
<commit_msg>Limit number of attempts<commit_after>const PRIORITY_EVENT_TITLE_PREFIXES = ['1.', '2.', '3.', '4.', '5.'];

function dispatchEvent(obj, event) {
    var evt = new Event(
        event,
        {
            target: obj,
            bubbles: true,
        }
    );
    obj.dispatchEvent(evt);
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertButtons(referenceNode, onclickAction, where, alternateReferenceNode) {
    var insertTarget = referenceNode;
    var buttonLabels = [
        // buttonLabel, eventTitlePrefix, buttonClassNames.
        ['1',       '1.', ['jfk-button', 'jfk-button-standard']],
        ['2',       '2.', ['jfk-button', 'jfk-button-standard']],
        ['3',       '3.', ['jfk-button', 'jfk-button-standard']],
        ['4',       '4.', ['jfk-button', 'jfk-button-standard']],
        ['5',       '5.', ['jfk-button', 'jfk-button-standard']],
        ['DONE',    '✓',  ['jfk-button', 'jfk-button-default' ]],
        ['NOPE',    '✗',  ['jfk-button', 'jfk-button-standard']],
        ['OKAY',    '▣',  ['jfk-button', 'jfk-button-standard']],
        ['AWESOME', 'ツ', ['jfk-button', 'jfk-button-standard']],
    ];
    for (var i in buttonLabels) {
        var buttonLabel = buttonLabels[i][0];
        console.log('buttonLabel:', buttonLabel);

        var eventTitlePrefix = buttonLabels[i][1];
        console.log('eventTitlePrefix:', eventTitlePrefix);

        var buttonClassNames = buttonLabels[i][2];
        console.log('buttonClassNames:', buttonClassNames);

        var button = document.createElement('button');
        button.innerText = buttonLabel;
        button.classList.add(...buttonClassNames);

        (function(myButton, myEventTitlePrefix) {
            myButton.onclick = function() {
                console.log('myButton.onclick');
                onclickAction(myButton, myEventTitlePrefix);
            };
        }(button, eventTitlePrefix));

        console.log('inserting button', button, where, insertTarget);
        if (where === 'after') {
            insertAfter(button, insertTarget);
            insertTarget = button;
        } else if (where === 'inside') {
            if (alternateReferenceNode && !PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
                alternateReferenceNode.appendChild(button);
            } else {
                referenceNode.appendChild(button);
            }
        }
    }
}

function clickButton(clickedData) {
    console.group('clickButton');
    console.log('clickedData:', clickedData);
    console.log('button clicked');

    // Today.
    var date = new Date();
    var monthName = date.toLocaleString('en-us', { 'month': 'short' });
    var todayFormattedDate = monthName + ' ' + date.getDate() + ', ' + date.getFullYear();

    // Event date.
    var startDateInput = document.querySelector('[aria-label="Start date"]');
    var endDateInput = document.querySelector('[aria-label="End date"]');
    var eventDate = startDateInput.value;
    if (endDateInput.value !== startDateInput.value) {
        eventDate += ' - ' + endDateInput.value;
    }
    console.log('eventDate:', eventDate);

    var eventTitle = document.querySelector('[aria-label="Title"]');
    var calendarEventTitle = eventTitle.value;
    console.log('before calendarEventTitle:', calendarEventTitle);

    // Remove leading number (e.g. "1. " in "1. My Calendar Event").
    calendarEventTitle = calendarEventTitle.replace(/^\d+\. /, '');
    // Remove leading ~ character.
    calendarEventTitle = calendarEventTitle.replace(/^~ /, '');

    console.log(' after calendarEventTitle:', calendarEventTitle);

    // "✓ My Event; Dec 31, 2015; event date: Jan 1, 2016"
    // "1. My Event"
    var eventTitlePrefix = clickedData['eventTitlePrefix'];
    var newCalendarEventTitle = eventTitlePrefix + ' ' + calendarEventTitle;
    if (! PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
        newCalendarEventTitle += ';' +
            ' ' + todayFormattedDate + ';' +
            ' event date: ' + eventDate;
    }
    console.log('newCalendarEventTitle:', newCalendarEventTitle);
    eventTitle.value = newCalendarEventTitle;

    dispatchEvent(eventTitle, 'input');

    var saveButton = document.querySelector('[aria-label="Save"]');
    setTimeout(function() {
        saveButton.click();
    }, 500);

    console.groupEnd();
}

var buttonClickedData;
(function(){
    var pathname = window.location.pathname;
    setInterval(function() {
        if (pathname !== window.location.pathname) {
            console.log('pathname changed');
            pathname = window.location.pathname;
            if (pathname.match(/^\/calendar\/r\/eventedit\//)) {
                console.log('on event edit page');

                if (buttonClickedData) {
                    clickButton(buttonClickedData);
                    buttonClickedData = null;
                } else {
                    // Add action buttons to calendar event edit page.
                    console.log('adding buttons');
                    var onclickAction = function(myButton, myEventTitlePrefix) {
                        buttonClickedData = {
                            'button': myButton,
                            'eventTitlePrefix': myEventTitlePrefix,
                        };
                        clickButton(buttonClickedData);
                        buttonClickedData = null;
                    };

                    var saveButton = document.querySelector('[aria-label="Save"]');

                    var buttonWrapper = document.createElement('div');
                    buttonWrapper.style.left = '350px';
                    buttonWrapper.style.position = 'absolute';
                    buttonWrapper.style.top = '65px';
                    buttonWrapper.style.width = '350px';

                    insertAfter(buttonWrapper, saveButton);
                    insertButtons(buttonWrapper, onclickAction, 'inside');
                }
            }
        }
    }, 1000);
})();

document.onclick = function(event) {
    console.group('element clicked');
    console.log('element clicked:', event.target);

    var target = event.target;
    var eventIdFound = false;
    for (var i = 0; i < 5; i++) {
        // Ignore event bubble.
        if (target.getAttribute('id') === 'xDetDlg') {
            break;
        } else if (target.getAttribute('data-eventid')) {
            eventIdFound = true;
            break;
        }
        target = target.parentElement;
    }
    console.log('target:', target);
    if (eventIdFound) {
        var eventId = target.getAttribute('data-eventid');
        console.log('event id found:', eventId);

        function modifyEventBubble(attempt) {
            attempt = attempt || 1;
            if (attempt >= 3) {
                return;
            }
            attempt += 1;

            var eventBubble = document.querySelector('#xDetDlg[data-eventid="' + eventId + '"]');
            console.log('eventBubble:', eventBubble);
            if (!eventBubble) {
                setTimeout(modifyEventBubble, 500, attempt);
                return;
            }

            var editEventButton = eventBubble.querySelector('[data-tooltip="Edit event"]');
            var eventEditable = editEventButton ? true : false;
            if (!eventEditable) {
                return;
            }

            var lastEventBubbleMetaItem = document.querySelector('#xDtlDlgCt > div:last-child');

            var newEventBubbleMetaItem = document.createElement('div');
            newEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(newEventBubbleMetaItem, lastEventBubbleMetaItem);

            var lastNewEventBubbleMetaItem = document.createElement('div');
            lastNewEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(lastNewEventBubbleMetaItem, newEventBubbleMetaItem);

            var onclickAction = function(myButton, myEventTitlePrefix) {
                buttonClickedData = {
                    'button': myButton,
                    'eventTitlePrefix': myEventTitlePrefix,
                };

                var editEventButton = document.querySelector('[aria-label="Edit event"]');
                console.log('editEventButton:', editEventButton);
                editEventButton.click();
            };
            insertButtons(newEventBubbleMetaItem, onclickAction, 'inside', lastNewEventBubbleMetaItem);
        }
        setTimeout(modifyEventBubble, 100);
    }
    console.groupEnd();
};
<|endoftext|>


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

/**
 * Directive for the EndExploration 'interaction'.
 *
 * IMPORTANT NOTE: The naming convention for customization args that are passed
 * into the directive is: the name of the parameter, followed by 'With',
 * followed by the name of the arg.
 */
oppia.directive('oppiaInteractiveEndExploration', [
  'oppiaHtmlEscaper', function(oppiaHtmlEscaper) {
    return {
      restrict: 'E',
      scope: {},
      templateUrl: 'interaction/EndExploration',
      controller: [
          '$scope', '$http', '$attrs', '$q', 'urlService',
          'explorationContextService', 'PAGE_CONTEXT',
          function(
            $scope, $http, $attrs, $q, urlService,
            explorationContextService, PAGE_CONTEXT) {
        $scope.isIframed = urlService.isIframed();
        $scope.isInEditorPreviewMode = (
          explorationContextService.getPageContext() === PAGE_CONTEXT.EDITOR);
        $scope.invalidExpIds = [];
        $scope.recommendedExplorationSummaries = [];

        var authorRecommendationsDeferred = $q.defer();
        var authorRecommendationsPromise = authorRecommendationsDeferred.promise;

        var authorRecommendedExplorationIds = oppiaHtmlEscaper.escapedJsonToObj(
          $attrs.recommendedExplorationIdsWithValue);
        if (authorRecommendedExplorationIds.length > 0) {
          $http({
            method: 'GET',
            url: '/explorationsummarieshandler/data',
            params: {
              stringified_exp_ids: JSON.stringify(authorRecommendedExplorationIds)
            }
          }).success(function(data) {
            var authorRecommendedExplorationSummaries = [];
            data.summaries.map(function(explorationSummary, index) {
              if (explorationSummary) {
                authorRecommendedExplorationSummaries.push(explorationSummary);
              } else {
                $scope.invalidExpIds.push(authorRecommendedExplorationIds[index]);
                authorRecommendedExplorationIds.splice(index, 1);
              }
            });

            $scope.recommendedExplorationIds = authorRecommendedExplorationIds;
            $scope.recommendedExplorationSummaries = authorRecommendedExplorationSummaries;
            authorRecommendationsDeferred.resolve();
          });
        } else {
          authorRecommendationsDeferred.resolve();
        }

        if (!$scope.isInEditorPreviewMode) {
          authorRecommendationsPromise.then(function() {
            var explorationId = explorationContextService.getExplorationId();
            $http({
              method: 'GET',
              url: '/explorehandler/recommendations/' + explorationId
            }).success(function(data) {
              var allRecommendedExplorationIds = data.recommended_exp_ids;
              var systemRecommendedExplorationIds = [];

              var filteredRecommendationExplorationIds =
                  allRecommendedExplorationIds.filter(function(value) {
                return ($scope.recommendedExplorationIds.indexOf(value) == -1);
              });

              var MAX_RECOMMENDATIONS = 8;
              var maxSystemRecommendations = MAX_RECOMMENDATIONS -
                $scope.recommendedExplorationIds.length;

              var filteredRecommendationsSize = filteredRecommendationExplorationIds.length;
              for (var i = 0; i < Math.min(filteredRecommendationsSize, maxSystemRecommendations); i++) {
                var randomIndex = Math.floor(
                  Math.random() * filteredRecommendationExplorationIds.length);
                var randomRecommendationId =
                  filteredRecommendationExplorationIds[randomIndex];
                systemRecommendedExplorationIds.push(randomRecommendationId);
                filteredRecommendationExplorationIds.splice(randomIndex, 1);
              }

              if (systemRecommendedExplorationIds.length > 0) {
                $http({
                  method: 'GET',
                  url: '/explorationsummarieshandler/data',
                  params: {
                    stringified_exp_ids: JSON.stringify(systemRecommendedExplorationIds)
                  }
                }).success(function(data) {
                  $scope.recommendedExplorationIds = (
                    $scope.recommendedExplorationIds.concat(systemRecommendedExplorationIds));
                  $scope.recommendedExplorationSummaries = (
                    $scope.recommendedExplorationSummaries.concat(data.summaries));
                });
              }
            });
          });
        }
      }]
    };
  }
]);

oppia.directive('oppiaResponseEndExploration', [
  'oppiaHtmlEscaper', function(oppiaHtmlEscaper) {
    return {
      restrict: 'E',
      scope: {},
      templateUrl: 'response/EndExploration',
      controller: ['$scope', '$attrs', function($scope, $attrs) {

      }]
    };
  }
]);
<commit_msg>Initialize recommendedExplorationIds list in EndExploration.js.<commit_after>// Copyright 2014 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Directive for the EndExploration 'interaction'.
 *
 * IMPORTANT NOTE: The naming convention for customization args that are passed
 * into the directive is: the name of the parameter, followed by 'With',
 * followed by the name of the arg.
 */
oppia.directive('oppiaInteractiveEndExploration', [
  'oppiaHtmlEscaper', function(oppiaHtmlEscaper) {
    return {
      restrict: 'E',
      scope: {},
      templateUrl: 'interaction/EndExploration',
      controller: [
          '$scope', '$http', '$attrs', '$q', 'urlService',
          'explorationContextService', 'PAGE_CONTEXT',
          function(
            $scope, $http, $attrs, $q, urlService,
            explorationContextService, PAGE_CONTEXT) {
        $scope.isIframed = urlService.isIframed();
        $scope.isInEditorPreviewMode = (
          explorationContextService.getPageContext() === PAGE_CONTEXT.EDITOR);
        $scope.invalidExpIds = [];
        $scope.recommendedExplorationIds = [];
        $scope.recommendedExplorationSummaries = [];

        var authorRecommendationsDeferred = $q.defer();
        var authorRecommendationsPromise = authorRecommendationsDeferred.promise;

        var authorRecommendedExplorationIds = oppiaHtmlEscaper.escapedJsonToObj(
          $attrs.recommendedExplorationIdsWithValue);
        if (authorRecommendedExplorationIds.length > 0) {
          $http({
            method: 'GET',
            url: '/explorationsummarieshandler/data',
            params: {
              stringified_exp_ids: JSON.stringify(authorRecommendedExplorationIds)
            }
          }).success(function(data) {
            var authorRecommendedExplorationSummaries = [];
            data.summaries.map(function(explorationSummary, index) {
              if (explorationSummary) {
                authorRecommendedExplorationSummaries.push(explorationSummary);
              } else {
                $scope.invalidExpIds.push(authorRecommendedExplorationIds[index]);
                authorRecommendedExplorationIds.splice(index, 1);
              }
            });

            $scope.recommendedExplorationIds = authorRecommendedExplorationIds;
            $scope.recommendedExplorationSummaries = authorRecommendedExplorationSummaries;
            authorRecommendationsDeferred.resolve();
          });
        } else {
          authorRecommendationsDeferred.resolve();
        }

        if (!$scope.isInEditorPreviewMode) {
          authorRecommendationsPromise.then(function() {
            var explorationId = explorationContextService.getExplorationId();
            $http({
              method: 'GET',
              url: '/explorehandler/recommendations/' + explorationId
            }).success(function(data) {
              var allRecommendedExplorationIds = data.recommended_exp_ids;
              var systemRecommendedExplorationIds = [];

              var filteredRecommendationExplorationIds =
                  allRecommendedExplorationIds.filter(function(value) {
                return ($scope.recommendedExplorationIds.indexOf(value) == -1);
              });

              var MAX_RECOMMENDATIONS = 8;
              var maxSystemRecommendations = MAX_RECOMMENDATIONS -
                $scope.recommendedExplorationIds.length;

              var filteredRecommendationsSize = filteredRecommendationExplorationIds.length;
              for (var i = 0; i < Math.min(filteredRecommendationsSize, maxSystemRecommendations); i++) {
                var randomIndex = Math.floor(
                  Math.random() * filteredRecommendationExplorationIds.length);
                var randomRecommendationId =
                  filteredRecommendationExplorationIds[randomIndex];
                systemRecommendedExplorationIds.push(randomRecommendationId);
                filteredRecommendationExplorationIds.splice(randomIndex, 1);
              }

              if (systemRecommendedExplorationIds.length > 0) {
                $http({
                  method: 'GET',
                  url: '/explorationsummarieshandler/data',
                  params: {
                    stringified_exp_ids: JSON.stringify(systemRecommendedExplorationIds)
                  }
                }).success(function(data) {
                  $scope.recommendedExplorationIds = (
                    $scope.recommendedExplorationIds.concat(systemRecommendedExplorationIds));
                  $scope.recommendedExplorationSummaries = (
                    $scope.recommendedExplorationSummaries.concat(data.summaries));
                });
              }
            });
          });
        }
      }]
    };
  }
]);

oppia.directive('oppiaResponseEndExploration', [
  'oppiaHtmlEscaper', function(oppiaHtmlEscaper) {
    return {
      restrict: 'E',
      scope: {},
      templateUrl: 'response/EndExploration',
      controller: ['$scope', '$attrs', function($scope, $attrs) {

      }]
    };
  }
]);
<|endoftext|>


<commit_before>var data = [
    "A year on Mercury is just 88 days long.",
    "Despite being farther from the Sun, Venus experiences higher temperatures than Mercury.",
    "Venus rotates counter-clockwise, possibly because of a collision in the past with an asteroid.",
    "On Mars, the Sun appears about half the size as it does on Earth.",
    "Earth is the only planet not named after a god.",
    "Jupiter has the shortest day of all the planets.",
    "The Milky Way galaxy will collide with the Andromeda Galaxy in about 5 billion years.",
    "The Sun contains 99.86% of the mass in the Solar System.",
    "The Sun is an almost perfect sphere.",
    "A total solar eclipse can happen once every 1 to 2 years. This makes them a rare event.",
    "Saturn radiates two and a half times more energy into space than it receives from the sun.",
    "The temperature inside the Sun can reach 15 million degrees Celsius.",
    "The Moon is moving approximately 3.8 cm away from our planet every year."
];

var SKILL_NAME = "American Space Facts";
var GET_FACT_MESSAGE = "Here's your fact: ";
var HELP_MESSAGE = "You can say tell me a space fact, or, you can say exit... What can I help you with?";
var HELP_REPROMPT = "What can I help you with?";
var STOP_MESSAGE = "Goodbye!";<commit_msg>Update facts-en-US.js<commit_after>var data = [
    "Benjamin Moore Paintsis an American company that produces paint. It is owned by Berkshire Hathaway. Founded in 1883.",
    "The Moore Brothers founded the company in Brooklyn, New York and now It is based in Montvale, New Jersey",
    "Benjamin Moore Paints Ranked 1 in Customer Satisfaction by J.D. Power and Associates in 2011 & 2012.",
    "Regal,Aura,Natura,MoorGard,Ben,Arborcoat,Ultra Spec,Insl-X,Corotech are major products of Benjamin moore paints.",
    "Benjamin Moore produces the highest-quality paints and finishes in the industry, and deliver them to independent retail locations",
    "Today, Benjamin Moore is the 128-year-old company is one of the largest paint makers in North America, with 7 plants, 22 distribution facilities, and roughly 4,000 independent retailers.",
    "Berkshire Hathaway, a company led by billionaire Warren Buffett, acquired Benjamin Moore",
    "Today, Benjamin Moore & Co, a Berkshire Hathaway company, is a high-performing, innovative manufacturer",
    "BENJAMIN MOORE INTRODUCES ULTRA SPEC SCUFF-X AS THE INDUSTRY'S FIRST SCUFF-RESISTANT PAINT",
    "Benjamin Moore Industry Leader Introduces 75 New Colors and First-Ever Soft Touch Matte Finish.",
    "BENJAMIN MOORE REVEALS “SHADOW” AS ITS COLOR OF THE YEAR 2017."
];

var SKILL_NAME = "American BMC Facts";
var GET_FACT_MESSAGE = "Here's your fact: ";
var HELP_MESSAGE = "You can say tell me a Benjamin moore fact, or, you can say exit... What can I help you with?";
var HELP_REPROMPT = "What can I help you with?";
var STOP_MESSAGE = "Goodbye!";
<|endoftext|>


<commit_before>import React from 'react'
import classNames from 'classnames'
import ErrorList from './ErrorList'

const FormField = props => {
  if (props.hidden) {
    return null
  }

  const fromGroupClass = classNames(
    'form-field',
    'form-group',
    {
      'mandatory': props.mandatory,
      'dirty': props.dirty,
      'has-error': props.error && props.touched
    }
  )

  const labelClass = classNames(
    'col-sm-4',
    'control-label',
    {
      'sr-only': !props.useLabel
    })

  const labelAlt = `${props.label} ${props.mandatory ? props.mandatoryTitle : ''}`

  const editableValueWrapperClass = classNames({
    'col-sm-8': props.useLabel,
    'col-sm-12': !props.useLabel
  })

  return (

        {props.label}

        {props.children}
        {props.touched && props.error && <ErrorList error={props.error}/>}

  )
}

FormField.defaultProps = {
  useLabel: true,
  mandatoryTitle: 'is a mandatory field'
}

FormField.propTypes = {
  id: React.PropTypes.string,
  label: React.PropTypes.string,
  mandatory: React.PropTypes.bool,
  mandatoryTitle: React.PropTypes.string,
  children: React.PropTypes.node,
  hidden: React.PropTypes.bool,
  touched: React.PropTypes.bool,
  dirty: React.PropTypes.bool,
  useLabel: React.PropTypes.bool,
  error: React.PropTypes.objectOf(React.PropTypes.arrayOf(React.PropTypes.string))
}

export default FormField
<commit_msg>refactor(tocco-ui): change formField label input ratio<commit_after>import React from 'react'
import classNames from 'classnames'
import ErrorList from './ErrorList'

const FormField = props => {
  if (props.hidden) {
    return null
  }

  const fromGroupClass = classNames(
    'form-field',
    'form-group',
    {
      'mandatory': props.mandatory,
      'dirty': props.dirty,
      'has-error': props.error && props.touched
    }
  )

  const labelClass = classNames(
    'col-sm-3',
    'control-label',
    {
      'sr-only': !props.useLabel
    })

  const labelAlt = `${props.label} ${props.mandatory ? props.mandatoryTitle : ''}`

  const editableValueWrapperClass = classNames({
    'col-sm-9': props.useLabel,
    'col-sm-12': !props.useLabel
  })

  return (

        {props.label}

        {props.children}
        {props.touched && props.error && <ErrorList error={props.error}/>}

  )
}

FormField.defaultProps = {
  useLabel: true,
  mandatoryTitle: 'is a mandatory field'
}

FormField.propTypes = {
  id: React.PropTypes.string,
  label: React.PropTypes.string,
  mandatory: React.PropTypes.bool,
  mandatoryTitle: React.PropTypes.string,
  children: React.PropTypes.node,
  hidden: React.PropTypes.bool,
  touched: React.PropTypes.bool,
  dirty: React.PropTypes.bool,
  useLabel: React.PropTypes.bool,
  error: React.PropTypes.objectOf(React.PropTypes.arrayOf(React.PropTypes.string))
}

export default FormField
<|endoftext|>