<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name ngHref
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in an href attribute will
 * make the link go to the wrong URL if the user clicks it before
 * Angular has a chance to replace the `{{hash}}` markup with its
 * value. Until Angular replaces the markup the link will be broken
 * and will most likely return a 404 error.
 *
 * The `ngHref` directive solves this problem.
 *
 * The wrong way to write it:
 * ```html
 * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * @element A
 * @param {template} ngHref any string which can contain `{{}}` markup.
 *
 * @example
 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
 * in links and their different behaviors:
    <example>
      <file name="index.html">
        <input ng-model="value" />

        link 1 (link, don't reload)

        link 2 (link, don't reload)

        link 3 (link, reload!)

        anchor (link, don't reload)

        anchor (no link)

        link (link, change location)
      </file>
      <file name="protractor.js" type="protractor">
        it('should execute ng-click but not reload when href without value', function() {
          element(by.id('link-1')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('1');
          expect(element(by.id('link-1')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when href empty string', function() {
          element(by.id('link-2')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('2');
          expect(element(by.id('link-2')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click and change url when ng-href specified', function() {
          expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);

          element(by.id('link-3')).click();

          // At this point, we navigate away from an Angular page, so we need
          // to use browser.driver to get the base webdriver.

          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/123$/);
            });
          }, 1000, 'page should navigate to /123');
        });

        xit('should execute ng-click but not reload when href empty string and name specified', function() {
          element(by.id('link-4')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('4');
          expect(element(by.id('link-4')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when no href but name specified', function() {
          element(by.id('link-5')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('5');
          expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
        });

        it('should only change url when only ng-href', function() {
          element(by.model('value')).clear();
          element(by.model('value')).sendKeys('6');
          expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);

          element(by.id('link-6')).click();

          // At this point, we navigate away from an Angular page, so we need
          // to use browser.driver to get the base webdriver.
          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/6$/);
            });
          }, 1000, 'page should navigate to /6');
        });
      </file>
    </example>
 */

/**
 * @ngdoc directive
 * @name ngSrc
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until Angular replaces the expression inside
 * `{{hash}}`. The `ngSrc` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * @element IMG
 * @param {template} ngSrc any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngSrcset
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until Angular replaces the expression inside
 * `{{hash}}`. The `ngSrcset` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/>
 * ```
 *
 * @element IMG
 * @param {template} ngSrcset any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngDisabled
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
 * ```html
 * <div ng-init="scope = { isDisabled: false }">
 *  <button disabled="{{scope.isDisabled}}">Disabled</button>
 * </div>
 * ```
 *
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as disabled. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngDisabled` directive solves this problem for the `disabled` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 *
 * @example
    <example>
      <file name="index.html">
        Click me to toggle: <input type="checkbox" ng-model="checked">

        <button ng-model="button" ng-disabled="checked">Button</button>
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle button', function() {
          expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
 *     then special attribute "disabled" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngChecked
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as checked. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngChecked` directive solves this problem for the `checked` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
    <example>
      <file name="index.html">
        Check me to check both: <input type="checkbox" ng-model="master">

        <input id="checkSlave" type="checkbox" ng-checked="master">
      </file>
      <file name="protractor.js" type="protractor">
        it('should check both checkBoxes', function() {
          expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
          element(by.model('master')).click();
          expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
 *     then special attribute "checked" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngReadonly
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as readonly. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
    <example>
      <file name="index.html">
        Check me to make text readonly: <input type="checkbox" ng-model="checked">

        <input type="text" ng-readonly="checked" value="I'm Angular"/>
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle readonly attr', function() {
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
 *     then special attribute "readonly" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngSelected
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as selected. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngSelected` directive solves this problem for the `selected` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 *
 * @example
    <example>
      <file name="index.html">
        Check me to select: <input type="checkbox" ng-model="selected">

        <select>
          <option>Hello!</option>
          <option id="greet" ng-selected="selected">Greetings!</option>
        </select>
      </file>
      <file name="protractor.js" type="protractor">
        it('should select Greetings!', function() {
          expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
          element(by.model('selected')).click();
          expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element OPTION
 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
 *     then special attribute "selected" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngOpen
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as open. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngOpen` directive solves this problem for the `open` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
     <example>
       <file name="index.html">
         Check me check multiple: <input type="checkbox" ng-model="open">

            Show/Hide me

       </file>
       <file name="protractor.js" type="protractor">
         it('should toggle open', function() {
           expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
           element(by.model('open')).click();
           expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
         });
       </file>
     </example>
 *
 * @element DETAILS
 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
 *     then special attribute "open" will be set on the element
 */

var ngAttributeAliasDirectives = {};

// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
  // binding to multiple is not supported
  if (propName == "multiple") return;

  var normalized = directiveNormalize('ng-' + attrName);
  ngAttributeAliasDirectives[normalized] = function() {
    return {
      priority: 100,
      link: function(scope, element, attr) {
        scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
          attr.$set(attrName, !!value);
        });
      }
    };
  };
});

// aliased input attrs are evaluated
forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
  ngAttributeAliasDirectives[ngAttr] = function() {
    return {
      priority: 100,
      link: function(scope, element, attr) {
        //special case ngPattern when a literal regular expression value
        //is used as the expression (this way we don't have to watch anything).
        if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
          var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
          if (match) {
            attr.$set("ngPattern", new RegExp(match[1], match[2]));
            return;
          }
        }

        scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
          attr.$set(ngAttr, value);
        });
      }
    };
  };
});

// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
  var normalized = directiveNormalize('ng-' + attrName);
  ngAttributeAliasDirectives[normalized] = function() {
    return {
      priority: 99, // it needs to run after the attributes are interpolated
      link: function(scope, element, attr) {
        var propName = attrName,
            name = attrName;

        if (attrName === 'href' &&
            toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
          name = 'xlinkHref';
          attr.$attr[name] = 'xlink:href';
          propName = null;
        }

        attr.$observe(normalized, function(value) {
          if (!value)
             return;

          attr.$set(name, value);

          // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
          // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
          // to set the property as well to achieve the desired effect.
          // we use attr[attrName] value since $set can sanitize the url.
          if (msie && propName) element.prop(propName, attr[name]);
        });
      }
    };
  };
});
<commit_msg>chore(tests): increase timeout for navigation in ng-href tests to avoid timeouts<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name ngHref
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in an href attribute will
 * make the link go to the wrong URL if the user clicks it before
 * Angular has a chance to replace the `{{hash}}` markup with its
 * value. Until Angular replaces the markup the link will be broken
 * and will most likely return a 404 error.
 *
 * The `ngHref` directive solves this problem.
 *
 * The wrong way to write it:
 * ```html
 * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * @element A
 * @param {template} ngHref any string which can contain `{{}}` markup.
 *
 * @example
 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
 * in links and their different behaviors:
    <example>
      <file name="index.html">
        <input ng-model="value" />

        link 1 (link, don't reload)

        link 2 (link, don't reload)

        link 3 (link, reload!)

        anchor (link, don't reload)

        anchor (no link)

        link (link, change location)
      </file>
      <file name="protractor.js" type="protractor">
        it('should execute ng-click but not reload when href without value', function() {
          element(by.id('link-1')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('1');
          expect(element(by.id('link-1')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when href empty string', function() {
          element(by.id('link-2')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('2');
          expect(element(by.id('link-2')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click and change url when ng-href specified', function() {
          expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);

          element(by.id('link-3')).click();

          // At this point, we navigate away from an Angular page, so we need
          // to use browser.driver to get the base webdriver.

          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/123$/);
            });
          }, 5000, 'page should navigate to /123');
        });

        xit('should execute ng-click but not reload when href empty string and name specified', function() {
          element(by.id('link-4')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('4');
          expect(element(by.id('link-4')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when no href but name specified', function() {
          element(by.id('link-5')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('5');
          expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
        });

        it('should only change url when only ng-href', function() {
          element(by.model('value')).clear();
          element(by.model('value')).sendKeys('6');
          expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);

          element(by.id('link-6')).click();

          // At this point, we navigate away from an Angular page, so we need
          // to use browser.driver to get the base webdriver.
          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/6$/);
            });
          }, 5000, 'page should navigate to /6');
        });
      </file>
    </example>
 */

/**
 * @ngdoc directive
 * @name ngSrc
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until Angular replaces the expression inside
 * `{{hash}}`. The `ngSrc` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
 * ```
 *
 * @element IMG
 * @param {template} ngSrc any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngSrcset
 * @restrict A
 * @priority 99
 *
 * @description
 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until Angular replaces the expression inside
 * `{{hash}}`. The `ngSrcset` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/>
 * ```
 *
 * @element IMG
 * @param {template} ngSrcset any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngDisabled
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
 * ```html
 * <div ng-init="scope = { isDisabled: false }">
 *  <button disabled="{{scope.isDisabled}}">Disabled</button>
 * </div>
 * ```
 *
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as disabled. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngDisabled` directive solves this problem for the `disabled` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 *
 * @example
    <example>
      <file name="index.html">
        Click me to toggle: <input type="checkbox" ng-model="checked">

        <button ng-model="button" ng-disabled="checked">Button</button>
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle button', function() {
          expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
 *     then special attribute "disabled" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngChecked
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as checked. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngChecked` directive solves this problem for the `checked` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
    <example>
      <file name="index.html">
        Check me to check both: <input type="checkbox" ng-model="master">

        <input id="checkSlave" type="checkbox" ng-checked="master">
      </file>
      <file name="protractor.js" type="protractor">
        it('should check both checkBoxes', function() {
          expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
          element(by.model('master')).click();
          expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
 *     then special attribute "checked" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngReadonly
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as readonly. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
    <example>
      <file name="index.html">
        Check me to make text readonly: <input type="checkbox" ng-model="checked">

        <input type="text" ng-readonly="checked" value="I'm Angular"/>
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle readonly attr', function() {
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
 *     then special attribute "readonly" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngSelected
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as selected. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngSelected` directive solves this problem for the `selected` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 *
 * @example
    <example>
      <file name="index.html">
        Check me to select: <input type="checkbox" ng-model="selected">

        <select>
          <option>Hello!</option>
          <option id="greet" ng-selected="selected">Greetings!</option>
        </select>
      </file>
      <file name="protractor.js" type="protractor">
        it('should select Greetings!', function() {
          expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
          element(by.model('selected')).click();
          expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element OPTION
 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
 *     then special attribute "selected" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngOpen
 * @restrict A
 * @priority 100
 *
 * @description
 * The HTML specification does not require browsers to preserve the values of boolean attributes
 * such as open. (Their presence means true and their absence means false.)
 * If we put an Angular interpolation expression into such an attribute then the
 * binding information would be lost when the browser removes the attribute.
 * The `ngOpen` directive solves this problem for the `open` attribute.
 * This complementary directive is not removed by the browser and so provides
 * a permanent reliable place to store the binding information.
 * @example
     <example>
       <file name="index.html">
         Check me check multiple: <input type="checkbox" ng-model="open">

            Show/Hide me

       </file>
       <file name="protractor.js" type="protractor">
         it('should toggle open', function() {
           expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
           element(by.model('open')).click();
           expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
         });
       </file>
     </example>
 *
 * @element DETAILS
 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
 *     then special attribute "open" will be set on the element
 */

var ngAttributeAliasDirectives = {};

// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
  // binding to multiple is not supported
  if (propName == "multiple") return;

  var normalized = directiveNormalize('ng-' + attrName);
  ngAttributeAliasDirectives[normalized] = function() {
    return {
      priority: 100,
      link: function(scope, element, attr) {
        scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
          attr.$set(attrName, !!value);
        });
      }
    };
  };
});

// aliased input attrs are evaluated
forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
  ngAttributeAliasDirectives[ngAttr] = function() {
    return {
      priority: 100,
      link: function(scope, element, attr) {
        //special case ngPattern when a literal regular expression value
        //is used as the expression (this way we don't have to watch anything).
        if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
          var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
          if (match) {
            attr.$set("ngPattern", new RegExp(match[1], match[2]));
            return;
          }
        }

        scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
          attr.$set(ngAttr, value);
        });
      }
    };
  };
});

// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
  var normalized = directiveNormalize('ng-' + attrName);
  ngAttributeAliasDirectives[normalized] = function() {
    return {
      priority: 99, // it needs to run after the attributes are interpolated
      link: function(scope, element, attr) {
        var propName = attrName,
            name = attrName;

        if (attrName === 'href' &&
            toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
          name = 'xlinkHref';
          attr.$attr[name] = 'xlink:href';
          propName = null;
        }

        attr.$observe(normalized, function(value) {
          if (!value)
             return;

          attr.$set(name, value);

          // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
          // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
          // to set the property as well to achieve the desired effect.
          // we use attr[attrName] value since $set can sanitize the url.
          if (msie && propName) element.prop(propName, attr[name]);
        });
      }
    };
  };
});
<|endoftext|>


<commit_before>"use strict";
var colored = Object.create(global.console);
var colors = {
    Reset: "\x1b[0m",
    Bright: "\x1b[1m",
    Dim: "\x1b[2m",
    Underscore: "\x1b[4m",
    Blink: "\x1b[5m",
    Reverse: "\x1b[7m",
    Hidden: "\x1b[8m",
    FgBlack: "\x1b[30m",
    FgRed: "\x1b[31m",
    FgGreen: "\x1b[32m",
    FgYellow: "\x1b[33m",
    FgBlue: "\x1b[34m",
    FgMagenta: "\x1b[35m",
    FgCyan: "\x1b[36m",
    FgWhite: "\x1b[37m",
    BgBlack: "\x1b[40m",
    BgRed: "\x1b[41m",
    BgGreen: "\x1b[42m",
    BgYellow: "\x1b[43m",
    BgBlue: "\x1b[44m",
    BgMagenta: "\x1b[45m",
    BgCyan: "\x1b[46m",
    BgWhite: "\x1b[47m",
    FgGray: "\x1b[90m",
    FgRed2: "\x1b[91m",
    FgGreen2: "\x1b[92m",
    FgYellow2: "\x1b[93m",
    FgBlue2: "\x1b[94m",
    FgPurple: "\x1b[95m",
    FgCyan2: "\x1b[96m",
    FgWhite2: "\x1b[97m",
    // test: "\x1b[97m",
    BgGray: "\x1b[100m",
    BgRed2: "\x1b[102m",
    BgGreen2: "\x1b[102m",
    BgYellow2: "\x1b[103m",
    BgBlue2: "\x1b[104m",
    BgMagenta2: "\x1b[105m",
    BgCyan2: "\x1b[106m",
    BgWhite2: "\x1b[107m",
};
var lastLogLength = 0, lastLogTime = 0;
[
    "info:提示:FgCyan:",
    "warn:警告:FgYellow:",
    "error:错误:FgRed2:"
].forEach(function (config) {
    var [log, info = log.toUpperCase(), fg, bg] = config.split(":");
    var fgColor = colors[fg] || "",
        bgColor = colors[bg] || "",
        reset = colors.Reset;
    var toLength = function (n, a = -1) {
        n = String(n);
        if (n.length < 2) {
            n = '0' + n;
        }
        if (a === -1 && n.length < 3) {
            n = '0' + n;
        }
        return colors.BgGray + colors.FgWhite2 + n + colors.BgGray + colors.FgWhite;
    };
    var formatDate = function () {
        var year = this.getFullYear();
        var month = this.getMonth() + 1;
        var date = this.getDate();
        var hours = this.getHours();
        var minutes = this.getMinutes();
        var seconds = this.getSeconds();
        var milli = this.getMilliseconds();
        milli = toLength(milli);
        var offset = -this.getTimezoneOffset() / 60;
        if (offset > 0) {
            offset = '+' + toLength(offset, 0);
        } else if (offset < 0) {
            offset = '-' + toLength(-offset, 0);
        } else {
            offset = '';
        }
        return `${[year, month, date].map(toLength).join('-') + colors.FgGray}T${[hours, minutes, seconds].map(toLength).join(':')}.${milli}Z${offset}`;
    };
    var logger = function (...args) {
        var label = fgColor + bgColor + info + reset;
        var time_stamp = '';
        if (new Date - lastLogTime > 60 * 1000) {
            lastLogTime = new Date;
            time_stamp = formatDate.call(lastLogTime) + reset + "\r\n";
        }
        var str = [time_stamp, label, ...args].join(" ");
        str = str.replace(/\<([a-z][\w]*)[^\>]*\>([\s\S]*?)\<\/\1\>/ig, function (_, c, s) {
            switch (c) {
                case "red":
                case "error":
                case "danger":
                    return colors.FgRed + s + colors.Reset;
                case "info":
                case "tip":
                case "blue":
                    return colors.FgBlue + s + colors.Reset;
                case "green":
                    return colors.FgGreen + s + colors.Reset;
                default:
                    c = c[0].toUpperCase() + c.slice(1).toLowerCase();
                    var k = "Fg" + c;
                    if (c in colors) k = c;
                    if (k in colors) {
                        return colors[k] + s + colors.Reset;
                    }
            }
            return s;
        });
        var width = process.stdout.columns;
        var hasNewLine = /^(warn|error)$/.test(log);
        var cleaner = ("\r" + " ".repeat(width - 1) + "\b".repeat(width - 1)).repeat(parseInt(lastLogLength / width) + 1);
        hasNewLine ? process.stdout.write((lastLogLength ? cleaner : "") + str + "\r\n") : process.stdout.write(cleaner + "\r" + str);
        lastLogLength = hasNewLine ? 0 : str.length + str.replace(/[\x20-\xff]/g, "").length;
    };
    colored[log] = logger;
});
module.exports = colored;<commit_msg>错误信息输出到stderr<commit_after>"use strict";
var colored = Object.create(global.console);
var colors = {
    Reset: "\x1b[0m",
    Bright: "\x1b[1m",
    Dim: "\x1b[2m",
    Underscore: "\x1b[4m",
    Blink: "\x1b[5m",
    Reverse: "\x1b[7m",
    Hidden: "\x1b[8m",
    FgBlack: "\x1b[30m",
    FgRed: "\x1b[31m",
    FgGreen: "\x1b[32m",
    FgYellow: "\x1b[33m",
    FgBlue: "\x1b[34m",
    FgMagenta: "\x1b[35m",
    FgCyan: "\x1b[36m",
    FgWhite: "\x1b[37m",
    BgBlack: "\x1b[40m",
    BgRed: "\x1b[41m",
    BgGreen: "\x1b[42m",
    BgYellow: "\x1b[43m",
    BgBlue: "\x1b[44m",
    BgMagenta: "\x1b[45m",
    BgCyan: "\x1b[46m",
    BgWhite: "\x1b[47m",
    FgGray: "\x1b[90m",
    FgRed2: "\x1b[91m",
    FgGreen2: "\x1b[92m",
    FgYellow2: "\x1b[93m",
    FgBlue2: "\x1b[94m",
    FgPurple: "\x1b[95m",
    FgCyan2: "\x1b[96m",
    FgWhite2: "\x1b[97m",
    // test: "\x1b[97m",
    BgGray: "\x1b[100m",
    BgRed2: "\x1b[102m",
    BgGreen2: "\x1b[102m",
    BgYellow2: "\x1b[103m",
    BgBlue2: "\x1b[104m",
    BgMagenta2: "\x1b[105m",
    BgCyan2: "\x1b[106m",
    BgWhite2: "\x1b[107m",
};
var lastLogLength = 0, lastLogTime = 0;
[
    "info:提示:FgCyan:",
    "warn:警告:FgYellow:",
    "error:错误:FgRed2:"
].forEach(function (config) {
    var [log, info = log.toUpperCase(), fg, bg] = config.split(":");
    var fgColor = colors[fg] || "",
        bgColor = colors[bg] || "",
        reset = colors.Reset;
    var toLength = function (n, a = -1) {
        n = String(n);
        if (n.length < 2) {
            n = '0' + n;
        }
        if (a === -1 && n.length < 3) {
            n = '0' + n;
        }
        return colors.BgGray + colors.FgWhite2 + n + colors.BgGray + colors.FgWhite;
    };
    var formatDate = function () {
        var year = this.getFullYear();
        var month = this.getMonth() + 1;
        var date = this.getDate();
        var hours = this.getHours();
        var minutes = this.getMinutes();
        var seconds = this.getSeconds();
        var milli = this.getMilliseconds();
        milli = toLength(milli);
        var offset = -this.getTimezoneOffset() / 60;
        if (offset > 0) {
            offset = '+' + toLength(offset, 0);
        } else if (offset < 0) {
            offset = '-' + toLength(-offset, 0);
        } else {
            offset = '';
        }
        return `${[year, month, date].map(toLength).join('-') + colors.FgGray}T${[hours, minutes, seconds].map(toLength).join(':')}.${milli}Z${offset}`;
    };
    var logger = function (...args) {
        var label = fgColor + bgColor + info + reset;
        var time_stamp = '';
        if (new Date - lastLogTime > 60 * 1000) {
            lastLogTime = new Date;
            time_stamp = formatDate.call(lastLogTime) + reset + "\r\n";
        }
        var str = [time_stamp, label, ...args].join(" ");
        str = str.replace(/\<([a-z][\w]*)[^\>]*\>([\s\S]*?)\<\/\1\>/ig, function (_, c, s) {
            switch (c) {
                case "red":
                case "error":
                case "danger":
                    return colors.FgRed + s + colors.Reset;
                case "info":
                case "tip":
                case "blue":
                    return colors.FgBlue + s + colors.Reset;
                case "green":
                    return colors.FgGreen + s + colors.Reset;
                default:
                    c = c[0].toUpperCase() + c.slice(1).toLowerCase();
                    var k = "Fg" + c;
                    if (c in colors) k = c;
                    if (k in colors) {
                        return colors[k] + s + colors.Reset;
                    }
            }
            return s;
        });
        var width = process.stdout.columns;
        var hasNewLine = /^(warn|error)$/.test(log);
        var cleaner = ("\r" + " ".repeat(width - 1) + "\b".repeat(width - 1)).repeat(parseInt(lastLogLength / width) + 1);
        hasNewLine ? process.stderr.write((lastLogLength ? cleaner : "") + str + "\r\n") : process.stdout.write(cleaner + "\r" + str);
        lastLogLength = hasNewLine ? 0 : str.length + str.replace(/[\x20-\xff]/g, "").length;
    };
    colored[log] = logger;
});
module.exports = colored;<|endoftext|>


<commit_before>/* eslint-disable unicorn/no-process-exit */

// Native
import fs from 'fs'
import path from 'path'
import { spawnSync } from 'child_process'
import zlib from 'zlib'

// Packages
import onDeath from 'death'
import fetch from 'node-fetch'
import retry from 'async-retry'
import which from 'which-promise'
import readPkg from 'read-pkg'

// Utilities
import plusxSync from './chmod'
import {
  disableProgress,
  enableProgress,
  info,
  showProgress,
  warn
} from './log'

fetch.Promise = Promise
global.Promise = Promise
let { platform } = process
if (detectAlpine()) platform = 'alpine'

const packageDir = path.join(__dirname, '..', '..')
const packageJSON = readPkg.sync(packageDir)

const now = path.join(__dirname, 'now')
const targetWin32 = path.join(__dirname, 'now.exe')
const target = platform === 'win32' ? targetWin32 : now
const partial = target + '.partial'
const backup = target + '.' + packageJSON.version + '.backup'

const platformToName = {
  alpine: 'now-alpine',
  darwin: 'now-macos',
  linux: 'now-linux',
  win32: 'now-win.exe'
}

function detectAlpine () {
  if (platform !== 'linux') return false
  // https://github.com/sass/node-sass/issues/1589#issuecomment-265292579
  const ldd = spawnSync('ldd', [ process.execPath ]).stdout.toString()
  return /\bmusl\b/.test(ldd)
}

async function download() {
  try {
    fs.writeFileSync(
      now,
      '#!/usr/bin/env node\n' +
        'console.log("Please wait until the \'now\' installation completes!")\n'
    )
  } catch (err) {
    if (err.code === 'EACCES') {
      warn(
        'Please try installing Now CLI again with the `--unsafe-perm` option.'
      )
      info('Example: `npm i -g --unsafe-perm now`')

      process.exit()
    }

    throw err
  }

  onDeath(() => {
    fs.writeFileSync(
      now,
      '#!/usr/bin/env node\n' +
        'console.log("The \'now\' installation did not complete successfully.")\n' +
        'console.log("Please run \'npm i -g now\' to reinstall!")\n'
    )
    process.exit()
  })

  info('For the source code, check out: https://github.com/zeit/now-cli')

  // Print an empty line
  console.log('')

  await retry(async () => {
    console.log('Downloading Now CLI ' + packageJSON.version)

    try {
      const name = platformToName[platform]
      const url = `https://cdn.zeit.co/releases/now-cli/${packageJSON.version}/${name}`
      const resp = await fetch(url, { compress: false })

      if (resp.status !== 200) {
        throw new Error(resp.statusText + ' ' + url)
      }

      const ws = fs.createWriteStream(partial)

      await new Promise((resolve, reject) => {
        let bytesRead = 0

        resp.body
          .on('error', reject)
          .on('data', chunk => {
            bytesRead += chunk.length
          })

        const encoding = resp.headers.get('content-encoding')

        if (encoding && encoding === 'gzip') {
          const gunzip = zlib.createGunzip()

          gunzip
            .on('error', reject)

          resp.body.pipe(gunzip).pipe(ws)
        } else {
          resp.body.pipe(ws)
        }

        ws
          .on('error', reject)
          .on('close', () => {
            resolve()
          })
      })
    } finally {
      disableProgress()
    }
  }, {
    retries: 500,
    onRetry: (err) => console.error(err)
  })

  fs.renameSync(partial, target)
  fs.writeFileSync(backup, fs.readFileSync(target))
}

function modifyGitBashFile (content) {
  return (
    '#!/bin/sh\n' +
      'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")\n' +
      '\n' +
      'case `uname` in\n' +
      '    *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' +
      'esac\n' +
      '\n' +
    content.replace(
      'download/dist/now"', 'download/dist/now.exe"'));
}

async function main() {
  if (fs.existsSync(backup)) {
    fs.writeFileSync(target, fs.readFileSync(backup))
  } else {
    await download()
  }

  if (platform === 'win32') {
    try {
      fs.writeFileSync(now, '')
      // Workaround for https://github.com/npm/cmd-shim/pull/25
      const globalPath = path.dirname(await which('npm'))
      let gitBashFile = path.join(globalPath, 'now')
      if (!fs.existsSync(gitBashFile)) {
        gitBashFile = path.join(process.env.APPDATA, 'npm/now');
      }

      fs.writeFileSync(
        gitBashFile, modifyGitBashFile(fs.readFileSync(gitBashFile, 'utf8'))
      )
    } catch (err) {
      if (err.code !== 'ENOENT') {
        // Not a problem. only git cmd will not work
        console.error(err)
      }
    }
  } else {
    plusxSync(now)
  }
}

main().catch(err => {
  console.error(err)
  process.exit(2)
})
<commit_msg>Revert "Temporarily disable progress bar"<commit_after>/* eslint-disable unicorn/no-process-exit */

// Native
import fs from 'fs'
import path from 'path'
import { spawnSync } from 'child_process'
import zlib from 'zlib'

// Packages
import onDeath from 'death'
import fetch from 'node-fetch'
import retry from 'async-retry'
import which from 'which-promise'
import readPkg from 'read-pkg'

// Utilities
import plusxSync from './chmod'
import {
  disableProgress,
  enableProgress,
  info,
  showProgress,
  warn
} from './log'

fetch.Promise = Promise
global.Promise = Promise
let { platform } = process
if (detectAlpine()) platform = 'alpine'

const packageDir = path.join(__dirname, '..', '..')
const packageJSON = readPkg.sync(packageDir)

const now = path.join(__dirname, 'now')
const targetWin32 = path.join(__dirname, 'now.exe')
const target = platform === 'win32' ? targetWin32 : now
const partial = target + '.partial'
const backup = target + '.' + packageJSON.version + '.backup'

const platformToName = {
  alpine: 'now-alpine',
  darwin: 'now-macos',
  linux: 'now-linux',
  win32: 'now-win.exe'
}

function detectAlpine () {
  if (platform !== 'linux') return false
  // https://github.com/sass/node-sass/issues/1589#issuecomment-265292579
  const ldd = spawnSync('ldd', [ process.execPath ]).stdout.toString()
  return /\bmusl\b/.test(ldd)
}

async function download() {
  try {
    fs.writeFileSync(
      now,
      '#!/usr/bin/env node\n' +
        'console.log("Please wait until the \'now\' installation completes!")\n'
    )
  } catch (err) {
    if (err.code === 'EACCES') {
      warn(
        'Please try installing Now CLI again with the `--unsafe-perm` option.'
      )
      info('Example: `npm i -g --unsafe-perm now`')

      process.exit()
    }

    throw err
  }

  onDeath(() => {
    fs.writeFileSync(
      now,
      '#!/usr/bin/env node\n' +
        'console.log("The \'now\' installation did not complete successfully.")\n' +
        'console.log("Please run \'npm i -g now\' to reinstall!")\n'
    )
    process.exit()
  })

  info('For the source code, check out: https://github.com/zeit/now-cli')

  // Print an empty line
  console.log('')

  await retry(async () => {
    enableProgress('Downloading Now CLI ' + packageJSON.version)
    showProgress(0)

    try {
      const name = platformToName[platform]
      const url = `https://cdn.zeit.co/releases/now-cli/${packageJSON.version}/${name}`
      const resp = await fetch(url, { compress: false })

      if (resp.status !== 200) {
        throw new Error(resp.statusText + ' ' + url)
      }

      const size = resp.headers.get('content-length')

      if (!size) {
        throw new Error('Not found (content-length is absent)')
      }

      const ws = fs.createWriteStream(partial)

      await new Promise((resolve, reject) => {
        let bytesRead = 0

        resp.body
          .on('error', reject)
          .on('data', chunk => {
            bytesRead += chunk.length
            showProgress(100 * bytesRead / size)
          })

        const encoding = resp.headers.get('content-encoding')

        if (encoding && encoding === 'gzip') {
          const gunzip = zlib.createGunzip()

          gunzip
            .on('error', reject)

          resp.body.pipe(gunzip).pipe(ws)
        } else {
          resp.body.pipe(ws)
        }

        ws
          .on('error', reject)
          .on('close', () => {
            showProgress(100)
            resolve()
          })
      })
    } finally {
      disableProgress()
    }
  }, {
    retries: 500,
    onRetry: (err) => console.error(err)
  })

  fs.renameSync(partial, target)
  fs.writeFileSync(backup, fs.readFileSync(target))
}

function modifyGitBashFile (content) {
  return (
    '#!/bin/sh\n' +
      'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")\n' +
      '\n' +
      'case `uname` in\n' +
      '    *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' +
      'esac\n' +
      '\n' +
    content.replace(
      'download/dist/now"', 'download/dist/now.exe"'));
}

async function main() {
  if (fs.existsSync(backup)) {
    fs.writeFileSync(target, fs.readFileSync(backup))
  } else {
    await download()
  }

  if (platform === 'win32') {
    try {
      fs.writeFileSync(now, '')
      // Workaround for https://github.com/npm/cmd-shim/pull/25
      const globalPath = path.dirname(await which('npm'))
      let gitBashFile = path.join(globalPath, 'now')
      if (!fs.existsSync(gitBashFile)) {
        gitBashFile = path.join(process.env.APPDATA, 'npm/now');
      }

      fs.writeFileSync(
        gitBashFile, modifyGitBashFile(fs.readFileSync(gitBashFile, 'utf8'))
      )
    } catch (err) {
      if (err.code !== 'ENOENT') {
        // Not a problem. only git cmd will not work
        console.error(err)
      }
    }
  } else {
    plusxSync(now)
  }
}

main().catch(err => {
  console.error(err)
  process.exit(2)
})
<|endoftext|>


<commit_before>/*
 * create-server-test.js : namespace socket unit test for TLS.
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    fs = require('fs'),
    net = require('net'),
    path = require('path'),
    tls = require('tls'),
    vows = require('vows'),
    nssocket = require('../lib/nssocket');

function getBatch() {
  var args = [],
      res = {};

  for (var i = 0; i < arguments.length; i++) {
    args.push(arguments[i]);
  }

  return {
    "the createServer() method": {
      topic: function () {
        var outbound = new nssocket.NsSocket(),
            server = nssocket.createServer(this.callback.bind(null, null, outbound));

        server.listen.apply(server, args.concat(function () {
          outbound.connect.apply(outbound, args);
        }));
      },
      "should create a full-duplex namespaced socket": {
        topic: function (outbound, inbound) {
          outbound.on(['data', 'here', 'is'], this.callback.bind(outbound, null));
          inbound.send(['here', 'is'], 'something.');
        },
        "should handle namespaced events": function (_, data) {
          assert.isArray(this.event);
          assert.lengthOf(this.event, 3);
          assert.isString(this.event[0]);
          assert.isString(this.event[1]);
          assert.isString(this.event[2]);
          assert.isString(data);
          assert.equal(this.event[0], 'data');
          assert.equal(this.event[1], 'here');
          assert.equal(this.event[2], 'is');
          assert.equal(data, 'something.');
        }
      }
    }
  };
}

var PORT = 9564,
    HOST = "127.0.0.1",
    PIPE = path.join(__dirname, "fixtures", "nssocket.sock");

vows.describe('nssocket/create-server').addBatch({
  "When using NsSocket": {
    "with `(PORT)` argument": getBatch(PORT),
    "with `(PORT, HOST)` arguments": getBatch(PORT + 1, HOST),
    "with `(PIPE)` argument": getBatch(PIPE)
  }
}).addBatch({
  "When tests are finished": {
    "`PIPE` should be removed": function () {
      fs.unlinkSync(PIPE);
    }
  }
}).export(module);

<commit_msg>[test minor] Arguments can be normalized easier<commit_after>/*
 * create-server-test.js : namespace socket unit test for TLS.
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    fs = require('fs'),
    net = require('net'),
    path = require('path'),
    tls = require('tls'),
    vows = require('vows'),
    nssocket = require('../lib/nssocket');

function getBatch() {
  var args = Array.prototype.slice.call(arguments),
      res = {};

  return {
    "the createServer() method": {
      topic: function () {
        var outbound = new nssocket.NsSocket(),
            server = nssocket.createServer(this.callback.bind(null, null, outbound));

        server.listen.apply(server, args.concat(function () {
          outbound.connect.apply(outbound, args);
        }));
      },
      "should create a full-duplex namespaced socket": {
        topic: function (outbound, inbound) {
          outbound.on(['data', 'here', 'is'], this.callback.bind(outbound, null));
          inbound.send(['here', 'is'], 'something.');
        },
        "should handle namespaced events": function (_, data) {
          assert.isArray(this.event);
          assert.lengthOf(this.event, 3);
          assert.isString(this.event[0]);
          assert.isString(this.event[1]);
          assert.isString(this.event[2]);
          assert.isString(data);
          assert.equal(this.event[0], 'data');
          assert.equal(this.event[1], 'here');
          assert.equal(this.event[2], 'is');
          assert.equal(data, 'something.');
        }
      }
    }
  };
}

var PORT = 9564,
    HOST = "127.0.0.1",
    PIPE = path.join(__dirname, "fixtures", "nssocket.sock");

vows.describe('nssocket/create-server').addBatch({
  "When using NsSocket": {
    "with `(PORT)` argument": getBatch(PORT),
    "with `(PORT, HOST)` arguments": getBatch(PORT + 1, HOST),
    "with `(PIPE)` argument": getBatch(PIPE)
  }
}).addBatch({
  "When tests are finished": {
    "`PIPE` should be removed": function () {
      fs.unlinkSync(PIPE);
    }
  }
}).export(module);

<|endoftext|>


<commit_before>import { messageTypes } from './messageTypes'
import { actions } from 'client/actions/articlesActions'

const {
  articlesRequested,
  articleLocked,
  userStartedEditing,
  userStoppedEditing,
  userCurrentlyEditing
} = messageTypes

export const articlesInSession = {}

export const onArticlesRequested = ({io, socket}) => {
  console.log('[socket] onArticlesRequested')
  const event = articlesRequested
  socket.emit(event, {
    type: actions.EDITED_ARTICLES_RECEIVED,
    payload: articlesInSession
  })
}

export const onUserStartedEditing = ({io, socket}, data) => {
  console.log('[socket] onUserStartedEditing, userId: ', data.user.id, ', article: ', data.article)
  const { timestamp, user, article } = data
  const { id, name } = user
  const currentSession = articlesInSession[article]
  if (currentSession && currentSession.user.id !== id) {
    socket.emit(articleLocked, {
      type: 'ARTICLE_LOCKED',
      payload: articlesInSession[article]
    })
    return
  }

  const newSession = articlesInSession[article] = {
    timestamp,
    user: {
      id,
      name
    },
    article
  }

  io.sockets.emit(userStartedEditing, {
    type: data.type,
    payload: newSession
  })
}

export const onUserCurrentlyEditing = ({io, socket}, data) => {
  const { article, timestamp } = data
  articlesInSession[article].timestamp = timestamp

  const event = articlesRequested
  io.sockets.emit(event, {
    type: actions.EDITED_ARTICLES_RECEIVED,
    payload: articlesInSession
  })
}

export const onUserStoppedEditing = ({io, socket}, data) => {
  console.log('[socket] onUserStoppedEditing, userId: ', data.user.id, ', article: ', data.article)
  const { article, user } = data
  const currentSession = articlesInSession[article]

  if (currentSession && currentSession.user.id !== user.id) {
    socket.emit(articleLocked, {
      type: 'ARTICLE_LOCKED',
      payload: articlesInSession[article]
    })
    return
  }
  delete articlesInSession[article]

  io.sockets.emit(userStoppedEditing, {
    type: data.type,
    payload: {
      article
    }
  })
}

function addListenersToSocket ({ io, socket }) {
  socket.on(articlesRequested, onArticlesRequested.bind(this, {io, socket}))
  socket.on(userStartedEditing, onUserStartedEditing.bind(this, {io, socket}))
  socket.on(userStoppedEditing, onUserStoppedEditing.bind(this, {io, socket}))
  socket.on(userCurrentlyEditing, onUserCurrentlyEditing.bind(this, {io, socket}))
}

export const init = (io) => {
  io.on('connection', (socket) => addListenersToSocket({io, socket}))
}
<commit_msg>[Dev] refix tests<commit_after>import { messageTypes } from './messageTypes'

const {
  articlesRequested,
  articleLocked,
  userStartedEditing,
  userStoppedEditing,
  userCurrentlyEditing
} = messageTypes

export const articlesInSession = {}

export const onArticlesRequested = ({io, socket}) => {
  console.log('[socket] onArticlesRequested')
  const event = articlesRequested
  socket.emit(event, {
    type: 'EDITED_ARTICLES_RECEIVED',
    payload: articlesInSession
  })
}

export const onUserStartedEditing = ({io, socket}, data) => {
  console.log('[socket] onUserStartedEditing, userId: ', data.user.id, ', article: ', data.article)
  const { timestamp, user, article } = data
  const { id, name } = user
  const currentSession = articlesInSession[article]
  if (currentSession && currentSession.user.id !== id) {
    socket.emit(articleLocked, {
      type: 'ARTICLE_LOCKED',
      payload: articlesInSession[article]
    })
    return
  }

  const newSession = articlesInSession[article] = {
    timestamp,
    user: {
      id,
      name
    },
    article
  }

  io.sockets.emit(userStartedEditing, {
    type: data.type,
    payload: newSession
  })
}

export const onUserCurrentlyEditing = ({io, socket}, data) => {
  const { article, timestamp } = data
  articlesInSession[article].timestamp = timestamp

  const event = articlesRequested
  io.sockets.emit(event, {
    type: 'EDITED_ARTICLES_RECEIVED',
    payload: articlesInSession
  })
}

export const onUserStoppedEditing = ({io, socket}, data) => {
  console.log('[socket] onUserStoppedEditing, userId: ', data.user.id, ', article: ', data.article)
  const { article, user } = data
  const currentSession = articlesInSession[article]

  if (currentSession && currentSession.user.id !== user.id) {
    socket.emit(articleLocked, {
      type: 'ARTICLE_LOCKED',
      payload: articlesInSession[article]
    })
    return
  }
  delete articlesInSession[article]

  io.sockets.emit(userStoppedEditing, {
    type: data.type,
    payload: {
      article
    }
  })
}

function addListenersToSocket ({ io, socket }) {
  socket.on(articlesRequested, onArticlesRequested.bind(this, {io, socket}))
  socket.on(userStartedEditing, onUserStartedEditing.bind(this, {io, socket}))
  socket.on(userStoppedEditing, onUserStoppedEditing.bind(this, {io, socket}))
  socket.on(userCurrentlyEditing, onUserCurrentlyEditing.bind(this, {io, socket}))
}

export const init = (io) => {
  io.on('connection', (socket) => addListenersToSocket({io, socket}))
}
<|endoftext|>


<commit_before>/* global describe, it */
'use strict';

var assert = require('assert');
var _ = require('lodash');

describe('#requires', function () {
  var getData = require('../../src/file').getData;

  var expected = require('./fixture/requires/expected');
  var input = 'test/data/fixture/requires';
  var data;

  before(function (done) {
    return getData(input).then(function (res) {
      data = res;
      done();
    });
  });

  it('should match expected data', function () {
    var data = _.clone(data);
    delete data['function']['test-function-requires'].requires.toJSON;
    assert.deepEqual(data, expected);
  });

  it('should be JSON serializable', function () {
    assert.equal(JSON.stringify(data, null, '  '), JSON.stringify(expected, null, '  '));
  });
});
<commit_msg>Fix var hoisting bug<commit_after>/* global describe, it */
'use strict';

var assert = require('assert');
var _ = require('lodash');

describe('#requires', function () {
  var getData = require('../../src/file').getData;

  var expected = require('./fixture/requires/expected');
  var input = 'test/data/fixture/requires';
  var data;

  before(function (done) {
    return getData(input).then(function (res) {
      data = res;
      done();
    });
  });

  it('should match expected data', function () {
    /**
     * Do not, never, ever, call this variable `data`.
     *
     * @see https://twitter.com/HugoGiraudel/status/503253144940601344
     */
    var dataClone = _.clone(data, true);

    delete dataClone['function']['test-function-requires'].requires.toJSON;
    assert.deepEqual(dataClone, expected);
  });

  it('should be JSON serializable', function () {
    assert.equal(JSON.stringify(data, null, '  '), JSON.stringify(expected, null, '  '));
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import Autosuggest from 'react-autosuggest';
import highlight from 'autosuggest-highlight';
import api from '../utils/api';

const titles = [
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMjEyMjcyNDI4MF5BMl5BanBnXkFtZTcwMDA5Mzg3OA@@._V1_SX300.jpg',
    Title:'The Godfather',
    Type:'movie',
    Year:'1972',
    imdbID:'tt0068646'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BNDc2NTM3MzU1Nl5BMl5BanBnXkFtZTcwMTA5Mzg3OA@@._V1_SX300.jpg',
    Title:'The Godfather: Part II',
    Type:'movie',
    Year:'1974',
    imdbID:'tt0071562'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMTczMDcxNDA4MV5BMl5BanBnXkFtZTgwNjY1NTk4NjE@._V1_SX300.jpg',
    Title:'The Godfather: Part III',
    Type:'movie',
    Year:'1990',
    imdbID:'tt0099674'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMjEyMjcyNDI4MF5BMl5BanBnXkFtZTcwMDA5Mzg3OA@@._V1_SX300.jpg',
    Title: 'The Godfather Trilogy: 1901-1980',
    Type:'movie',
    Year:'1992',
    imdbID:'tt0150742'
  }
];

function escapeRegexCharacters(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getSuggestionValue(suggestion) {
  return `${suggestion.Title} ${suggestion.Year}`;
}

function renderSuggestion(suggestion, { value, valueBeforeUpDown }) {
  const suggestionText = `${suggestion.Title} (${suggestion.Year})`;
  const query = (valueBeforeUpDown || value).trim();
  const matches = highlight.match(suggestionText, query);
  const parts = highlight.parse(suggestionText, matches);

  return (

        {
          parts.map((part, index) => {
            const className = part.highlight ? 'highlight' : null;

            return (
              {part.text}
            );
          })
        }

  );
}

class SearchBar extends React.Component {
  constructor() {
    super();

    this.state = {
      value: '',
      suggestions: this.getSuggestions(''),
      results: []
    };

    this.onChange = this.onChange.bind(this);
    this.onSuggestionsUpdateRequested = this.onSuggestionsUpdateRequested.bind(this);
    this.getSuggestions = this.getSuggestions.bind(this);
    this.search = this.search.bind(this);
    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
  }

  // TODO: Debounce API calls
  search() {
   if (this.state.value.trim().length > 2) {
      api.movieSearch(this.state.value)
        .then( response => {
          if (response.Search) {
            console.log('Response from API: ', response);
            this.setState(
              {results: response.Search},
              this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
            );
            console.log(this.state);
          } else {
            this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
          }
          console.log('Response from API: ', response);
          this.setState(
            {results: response.Search},
            this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
          );
          console.log('Searchbar state: ',this.state);
        });
   }
  }

  onChange(event, { newValue, method }) {
    // fires API Search request if method === 'type'
    if (method === 'type') {
      this.setState(
        { value: newValue },
        () => this.search()
      );;
    }
  }

  // TODO: implement onSuggestionSelected
onSuggestionSelected(event, { suggestion, suggestionValue, sectionIndex, method }) {
  // set value to suggestion.Title
  // fire idSearch if method === 'click'
}

  onSuggestionsUpdateRequested({ value, reason }) {
    this.setState({
      suggestions: this.getSuggestions(value)
    });
  }

  getSuggestions(value) {
    const escapedValue = escapeRegexCharacters(value.trim());

    if (escapedValue === '') {
      return [];
    }

    const regex = new RegExp('\\b' + escapedValue, 'i');

    if (this.state.results) {
      return this.state.results.filter(title => regex.test(getSuggestionValue(title)));
    }
    console.log()
    return [];
  }

  render() {
    const inputProps = {
      placeholder: 'Search...',
      value: this.state.value,
      onChange: this.onChange
    };

    return (
      <Autosuggest suggestions={this.state.suggestions}
                   onSuggestionsUpdateRequested={this.onSuggestionsUpdateRequested}
                   getSuggestionValue={getSuggestionValue}
                   renderSuggestion={renderSuggestion}
                   inputProps={inputProps}
                   onSuggestionSelected={this.onSuggestionSelected}
      />
    );
  }
}

// class SearchBar extends React.Component {
//   constructor(props){
//     super(props);
//     this.state = {
//       results: []
//     };
//   }

//   render(){
//     return (
//

//         <form>
//           <input
//             type='text'
//             placeholder='Search...'
//             value={this.props.query}
//             onChange={this.props.handleTextChange}
//           >
//           </input>
//           <input type='checkbox' onChange={this.props.handleMovieCheck} />
//
Movie

//           <input type='checkbox' onChange={this.props.handleSeriesCheck}/>
//
TV Series

//           <button
//             type='submit'
//             onClick={this.props.handleSearchSubmit}
//           >
//             Submit
//           </button>
//         </form>
//

//       );
//   }
// };

module.exports = SearchBar;<commit_msg>Add default artwork for missing posters<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import Autosuggest from 'react-autosuggest';
import highlight from 'autosuggest-highlight';
import api from '../utils/api';

const titles = [
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMjEyMjcyNDI4MF5BMl5BanBnXkFtZTcwMDA5Mzg3OA@@._V1_SX300.jpg',
    Title:'The Godfather',
    Type:'movie',
    Year:'1972',
    imdbID:'tt0068646'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BNDc2NTM3MzU1Nl5BMl5BanBnXkFtZTcwMTA5Mzg3OA@@._V1_SX300.jpg',
    Title:'The Godfather: Part II',
    Type:'movie',
    Year:'1974',
    imdbID:'tt0071562'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMTczMDcxNDA4MV5BMl5BanBnXkFtZTgwNjY1NTk4NjE@._V1_SX300.jpg',
    Title:'The Godfather: Part III',
    Type:'movie',
    Year:'1990',
    imdbID:'tt0099674'
  },
  {
    Poster: 'http://ia.media-imdb.com/images/M/MV5BMjEyMjcyNDI4MF5BMl5BanBnXkFtZTcwMDA5Mzg3OA@@._V1_SX300.jpg',
    Title: 'The Godfather Trilogy: 1901-1980',
    Type:'movie',
    Year:'1992',
    imdbID:'tt0150742'
  }
];

function escapeRegexCharacters(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getSuggestionValue(suggestion) {
  return `${suggestion.Title} ${suggestion.Year}`;
}

function renderSuggestion(suggestion, { value, valueBeforeUpDown }) {
  const suggestionText = `${suggestion.Title} (${suggestion.Year})`;
  const query = (valueBeforeUpDown || value).trim();
  const matches = highlight.match(suggestionText, query);
  const parts = highlight.parse(suggestionText, matches);

  return (

        {
          parts.map((part, index) => {
            const className = part.highlight ? 'highlight' : null;

            return (
              {part.text}
            );
          })
        }

  );
}

class SearchBar extends React.Component {
  constructor() {
    super();

    this.state = {
      value: '',
      suggestions: this.getSuggestions(''),
      results: []
    };

    this.onChange = this.onChange.bind(this);
    this.onSuggestionsUpdateRequested = this.onSuggestionsUpdateRequested.bind(this);
    this.getSuggestions = this.getSuggestions.bind(this);
    this.search = this.search.bind(this);
    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
  }

  // TODO: Debounce API calls
  search() {
   if (this.state.value.trim().length > 2) {
      api.movieSearch(this.state.value)
        .then( response => {
          if (response.Search) {
            console.log('Response from API: ', response);
            this.setState(
              {results: response.Search},
              this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
            );
            console.log(this.state);
          } else {
            this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
          }
          console.log('Response from API: ', response);
          this.setState(
            {results: response.Search},
            this.onSuggestionsUpdateRequested({value: this.state.value, reason:'type'})
          );
          console.log('Searchbar state: ',this.state);
        });
   }
  }

  onChange(event, { newValue, method }) {
    // fires API Search request if method === 'type'
    if (method === 'type') {
      this.setState(
        { value: newValue },
        () => this.search()
      );;
    }
  }

  // TODO: implement onSuggestionSelected
onSuggestionSelected(event, { suggestion, suggestionValue, sectionIndex, method }) {
  // set value to suggestion.Title
  // fire idSearch if method === 'click'
}

  onSuggestionsUpdateRequested({ value, reason }) {
    this.setState({
      suggestions: this.getSuggestions(value)
    });
  }

  getSuggestions(value) {
    const escapedValue = escapeRegexCharacters(value.trim());

    if (escapedValue === '') {
      return [];
    }

    const regex = new RegExp('\\b' + escapedValue, 'i');

    if (this.state.results) {
      return this.state.results.filter(title => regex.test(getSuggestionValue(title)));
    }
    console.log()
    return [];
  }

  render() {
    const inputProps = {
      placeholder: 'Search...',
      value: this.state.value,
      onChange: this.onChange
    };

    return (
      <Autosuggest suggestions={this.state.suggestions}
                   onSuggestionsUpdateRequested={this.onSuggestionsUpdateRequested}
                   getSuggestionValue={getSuggestionValue}
                   renderSuggestion={renderSuggestion}
                   inputProps={inputProps}
                   onSuggestionSelected={this.onSuggestionSelected}
      />
    );
  }
}

// class SearchBar extends React.Component {
//   constructor(props){
//     super(props);
//     this.state = {
//       results: []
//     };
//   }

//   render(){
//     return (
//

//         <form>
//           <input
//             type='text'
//             placeholder='Search...'
//             value={this.props.query}
//             onChange={this.props.handleTextChange}
//           >
//           </input>
//           <input type='checkbox' onChange={this.props.handleMovieCheck} />
//
Movie

//           <input type='checkbox' onChange={this.props.handleSeriesCheck}/>
//
TV Series

//           <button
//             type='submit'
//             onClick={this.props.handleSearchSubmit}
//           >
//             Submit
//           </button>
//         </form>
//

//       );
//   }
// };

module.exports = SearchBar;<|endoftext|>


<commit_before>/**
 * This defines FineUploaderBasic mode w/ support for uploading to S3, which provides all the basic
 * functionality of Fine Uploader Basic as well as code to handle uploads directly to S3.
 * Some inherited options and API methods have a special meaning in the context of the S3 uploader.
 */
qq.s3.FineUploaderBasic = function(o) {
    var options = {
        request: {
            signatureEndpoint: null,
            successEndpoint: null,
            accessKey: null,
            acl: 'private',

            // 'uuid', 'filename', or a function, which may be promissory
            key: 'uuid'
        }
    };

    // Replace any default options with user defined ones
    qq.extend(options, o, true);

    // Call base module
    qq.FineUploaderBasic.call(this, options);

    this.keys = [];
};

// Inherit basic public & private API methods.
qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePublicApi);
qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePrivateApi);

// Define public & private API methods for this module.
qq.extend(qq.s3.FineUploaderBasic.prototype, {
    /**
     * @param id File ID
     * @returns {*} Key name associated w/ the file, if one exists
     */
    getKey: function(id) {
        return this.keys[id];
    },

    /**
     * Override the parent's reset function to cleanup various S3-related items.
     */
    reset: function() {
        qq.FineUploaderBasic.prototype.reset.call(this);

        this.keys = [];
    },

    /**
     * Ensures the parent's upload handler creator passes any additional S3-specific options to the handler as well
     * as information required to instantiate the specific handler based on the current browser's capabilities.
     *
     * @returns {qq.UploadHandler}
     * @private
     */
    _createUploadHandler: function() {
        var additionalOptions = {
            getKeyName: qq.bind(this._determineKeyName, this),
            // pass size limit validation values to include in the request so AWS enforces this server-side
            validation: {
                minSizeLimit: this._options.validation.minSizeLimit,
                maxSizeLimit: this._options.validation.sizeLimit
            }
        };

        return qq.FineUploaderBasic.prototype._createUploadHandler.call(this, additionalOptions, "s3");
    },

    /**
     * Determine the file's key name and passes it to the caller via a promissory callback.  This also may
     * delegate to an integrator-defined function that determines the file's key name on demand,
     * which also may be promissory.
     *
     * @param id ID of the file
     * @param filename Name of the file
     * @returns {qq.Promise} A promise that will be fulfilled when the key name has been determined (and will be passed to the caller via the success callback).
     * @private
     */
    _determineKeyName: function(id, filename) {
        var self = this,
            promise = new qq.Promise(),
            keynameLogic = this._options.request.key,
            extension = qq.getExtension(filename),
            onGetKeynameFailure = promise.failure,
            onGetKeynameSuccess = function(keyname, extension) {
                var keynameToUse = keyname;

                if (extension !== undefined) {
                    self.keys[id] = keynameToUse + "." + extension;
                }
                else {
                    self.keys[id] = keynameToUse;
                }

                promise.success(self.keys[id]);
            };

        switch(keynameLogic) {
            case 'uuid':
                onGetKeynameSuccess(this.getUuid(id), extension);
                break;
            case 'filename':
                onGetKeynameSuccess(filename);
                break;
            default:
                if (qq.isFunction(keynameLogic)) {
                    this._handleKeynameFunction(keynameLogic, id, onGetKeynameSuccess, onGetKeynameFailure);
                }
                else {
                    this.log(keynameLogic + " is not a valid value for the s3.keyname option!", "error");
                    onGetKeynameFailure();
                }
        }

        return promise;
    },

    /**
     * Called by the internal onUpload handler if the integrator has supplied a function to determine
     * the file's key name.  The integrator's function may be promissory.  We also need to fulfill
     * the promise contract associated with the caller as well.
     *
     * @param keynameFunc Integrator-supplied function that must be executed to determine the key name.  May be promissory.
     * @param id ID of the associated file
     * @param successCallback Invoke this if key name retrieval is successful, passing in the key name.
     * @param failureCallback Invoke this if key name retrieval was unsuccessful.
     * @private
     */
    _handleKeynameFunction: function(keynameFunc, id, successCallback, failureCallback) {
        var onSuccess = function(keyname) {
                successCallback(keyname);
            },
            onFailure = function() {
                this.log('Failed to retrieve key name for ' + id, "error");
                failureCallback();
            },
            keyname = keynameFunc(id);

        if (qq.isPromise(keyname)) {
            keyname.then(onSuccess, onFailure);
        }
        else if (keyname == null) {
            onFailure();
        }
        else {
            onSuccess(keyname)
        }
    },

    /**
     * When the upload has completed, if it is successful, send a request to the `successEndpoint` (if defined).
     * This will hold up the call to the `onComplete` callback until we have determined success of the upload to S3
     * according to the local server, if a `successEndpoint` has been defined by the integrator.
     *
     * @param id ID of the completed upload
     * @param name Name of the associated item
     * @param result Object created from the server's parsed JSON response.
     * @param xhr Associated XmlHttpRequest, if this was used to send the request.
     * @returns {boolean || qq.Promise} true/false if success can be determined immediately, otherwise a `qq.Promise`
     * if we need to ask the server.
     * @private
     */
    _onComplete: function(id, name, result, xhr) {
        var success = result.success ? true : false,
            self = this,
            onCompleteArgs = arguments,
            key = this.getKey(id),
            successEndpoint = this._options.request.successEndpoint,
            cors = this._options.cors.expected,
            uuid = this.getUuid(id),
            bucket = qq.s3.util.getBucket(this._endpointStore.getEndpoint(id)),
            promise = new qq.Promise(),

            // If we are waiting for confirmation from the local server, and have received it,
            // include properties from the local server response in the `response` parameter
            // sent to the `onComplete` callback, delegate to the parent `_onComplete`, and
            // fulfill the associated promise.
            onSuccessFromServer = function(awsSuccessRequestResult) {
                qq.extend(result, awsSuccessRequestResult);
                qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);
                promise.success(awsSuccessRequestResult);
            },
            onFailureFromServer = function(awsSuccessRequestResult) {
                qq.extend(result, awsSuccessRequestResult);
                qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);
                promise.failure(awsSuccessRequestResult);
            },

            successAjaxRequestor;

        // Ask the local server if the file sent to S3 is ok.
        if (success && successEndpoint) {
            successAjaxRequestor = new qq.s3.UploadSuccessAjaxRequester({
                endpoint: successEndpoint,
                cors: cors,
                log: qq.bind(this.log, this)
            });

            successAjaxRequestor.sendSuccessRequest(id, {
                key: key,
                uuid: uuid,
                name: name,
                bucket: bucket
            })
                .then(onSuccessFromServer, onFailureFromServer);

            return promise;
        }

        // If we are not asking the local server about the file in S3, just delegate to the parent `_onComplete`.
        return qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
    },

    // Hooks into the base internal `_onSubmitDelete` to add key and bucket params to the delete file request.
    _onSubmitDelete: function(id, onSuccessCallback) {
        var additionalMandatedParams = {
            key: this.getKey(id),
            bucket: qq.s3.util.getBucket(this._endpointStore.getEndpoint(id))
        };

        qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback, additionalMandatedParams);
    }
});
<commit_msg>feat(client/js/s3/uploader.basic.js): #897 - Allow server to prevent retries in the response to request sent to success endpoint<commit_after>/**
 * This defines FineUploaderBasic mode w/ support for uploading to S3, which provides all the basic
 * functionality of Fine Uploader Basic as well as code to handle uploads directly to S3.
 * Some inherited options and API methods have a special meaning in the context of the S3 uploader.
 */
qq.s3.FineUploaderBasic = function(o) {
    var options = {
        request: {
            signatureEndpoint: null,
            successEndpoint: null,
            accessKey: null,
            acl: 'private',

            // 'uuid', 'filename', or a function, which may be promissory
            key: 'uuid'
        }
    };

    // Replace any default options with user defined ones
    qq.extend(options, o, true);

    // Call base module
    qq.FineUploaderBasic.call(this, options);

    this.keys = [];
};

// Inherit basic public & private API methods.
qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePublicApi);
qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePrivateApi);

// Define public & private API methods for this module.
qq.extend(qq.s3.FineUploaderBasic.prototype, {
    /**
     * @param id File ID
     * @returns {*} Key name associated w/ the file, if one exists
     */
    getKey: function(id) {
        return this.keys[id];
    },

    /**
     * Override the parent's reset function to cleanup various S3-related items.
     */
    reset: function() {
        qq.FineUploaderBasic.prototype.reset.call(this);

        this.keys = [];
    },

    /**
     * Ensures the parent's upload handler creator passes any additional S3-specific options to the handler as well
     * as information required to instantiate the specific handler based on the current browser's capabilities.
     *
     * @returns {qq.UploadHandler}
     * @private
     */
    _createUploadHandler: function() {
        var additionalOptions = {
            getKeyName: qq.bind(this._determineKeyName, this),
            // pass size limit validation values to include in the request so AWS enforces this server-side
            validation: {
                minSizeLimit: this._options.validation.minSizeLimit,
                maxSizeLimit: this._options.validation.sizeLimit
            }
        };

        return qq.FineUploaderBasic.prototype._createUploadHandler.call(this, additionalOptions, "s3");
    },

    /**
     * Determine the file's key name and passes it to the caller via a promissory callback.  This also may
     * delegate to an integrator-defined function that determines the file's key name on demand,
     * which also may be promissory.
     *
     * @param id ID of the file
     * @param filename Name of the file
     * @returns {qq.Promise} A promise that will be fulfilled when the key name has been determined (and will be passed to the caller via the success callback).
     * @private
     */
    _determineKeyName: function(id, filename) {
        var self = this,
            promise = new qq.Promise(),
            keynameLogic = this._options.request.key,
            extension = qq.getExtension(filename),
            onGetKeynameFailure = promise.failure,
            onGetKeynameSuccess = function(keyname, extension) {
                var keynameToUse = keyname;

                if (extension !== undefined) {
                    self.keys[id] = keynameToUse + "." + extension;
                }
                else {
                    self.keys[id] = keynameToUse;
                }

                promise.success(self.keys[id]);
            };

        switch(keynameLogic) {
            case 'uuid':
                onGetKeynameSuccess(this.getUuid(id), extension);
                break;
            case 'filename':
                onGetKeynameSuccess(filename);
                break;
            default:
                if (qq.isFunction(keynameLogic)) {
                    this._handleKeynameFunction(keynameLogic, id, onGetKeynameSuccess, onGetKeynameFailure);
                }
                else {
                    this.log(keynameLogic + " is not a valid value for the s3.keyname option!", "error");
                    onGetKeynameFailure();
                }
        }

        return promise;
    },

    /**
     * Called by the internal onUpload handler if the integrator has supplied a function to determine
     * the file's key name.  The integrator's function may be promissory.  We also need to fulfill
     * the promise contract associated with the caller as well.
     *
     * @param keynameFunc Integrator-supplied function that must be executed to determine the key name.  May be promissory.
     * @param id ID of the associated file
     * @param successCallback Invoke this if key name retrieval is successful, passing in the key name.
     * @param failureCallback Invoke this if key name retrieval was unsuccessful.
     * @private
     */
    _handleKeynameFunction: function(keynameFunc, id, successCallback, failureCallback) {
        var onSuccess = function(keyname) {
                successCallback(keyname);
            },
            onFailure = function() {
                this.log('Failed to retrieve key name for ' + id, "error");
                failureCallback();
            },
            keyname = keynameFunc(id);

        if (qq.isPromise(keyname)) {
            keyname.then(onSuccess, onFailure);
        }
        else if (keyname == null) {
            onFailure();
        }
        else {
            onSuccess(keyname)
        }
    },

    /**
     * When the upload has completed, if it is successful, send a request to the `successEndpoint` (if defined).
     * This will hold up the call to the `onComplete` callback until we have determined success of the upload to S3
     * according to the local server, if a `successEndpoint` has been defined by the integrator.
     *
     * @param id ID of the completed upload
     * @param name Name of the associated item
     * @param result Object created from the server's parsed JSON response.
     * @param xhr Associated XmlHttpRequest, if this was used to send the request.
     * @returns {boolean || qq.Promise} true/false if success can be determined immediately, otherwise a `qq.Promise`
     * if we need to ask the server.
     * @private
     */
    _onComplete: function(id, name, result, xhr) {
        var success = result.success ? true : false,
            self = this,
            onCompleteArgs = arguments,
            key = this.getKey(id),
            successEndpoint = this._options.request.successEndpoint,
            cors = this._options.cors.expected,
            uuid = this.getUuid(id),
            bucket = qq.s3.util.getBucket(this._endpointStore.getEndpoint(id)),
            promise = new qq.Promise(),

            // If we are waiting for confirmation from the local server, and have received it,
            // include properties from the local server response in the `response` parameter
            // sent to the `onComplete` callback, delegate to the parent `_onComplete`, and
            // fulfill the associated promise.
            onSuccessFromServer = function(awsSuccessRequestResult) {
                qq.extend(result, awsSuccessRequestResult);
                qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);
                promise.success(awsSuccessRequestResult);
            },
            onFailureFromServer = function(awsSuccessRequestResult) {
                qq.extend(result, awsSuccessRequestResult);

                // The server might not want the user to be able to re-send the file.
                if (result[self._options.retry.preventRetryResponseProperty]) {
                    self._preventRetries[id] = true;
                }

                qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);
                promise.failure(awsSuccessRequestResult);
            },

            successAjaxRequestor;

        // Ask the local server if the file sent to S3 is ok.
        if (success && successEndpoint) {
            successAjaxRequestor = new qq.s3.UploadSuccessAjaxRequester({
                endpoint: successEndpoint,
                cors: cors,
                log: qq.bind(this.log, this)
            });

            successAjaxRequestor.sendSuccessRequest(id, {
                key: key,
                uuid: uuid,
                name: name,
                bucket: bucket
            })
                .then(onSuccessFromServer, onFailureFromServer);

            return promise;
        }

        // If we are not asking the local server about the file in S3, just delegate to the parent `_onComplete`.
        return qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
    },

    // Hooks into the base internal `_onSubmitDelete` to add key and bucket params to the delete file request.
    _onSubmitDelete: function(id, onSuccessCallback) {
        var additionalMandatedParams = {
            key: this.getKey(id),
            bucket: qq.s3.util.getBucket(this._endpointStore.getEndpoint(id))
        };

        qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback, additionalMandatedParams);
    }
});
<|endoftext|>