<commit_before>/*!
 * Fancy Validate v0.1.2 - JavaScript Form Validation
 * Copyright 2012 cormin.lu@gmail.com
 * MIT Licensed
 * Build 05/05/2012
 */
(function($f) {
  var core = $f.core,
    dom = $f.dom,
    event = $f.event,

  $patterns = {
    /*RFC 2822 */
    email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,

    url: /^(http|https|ftp)\:\/\/[a-z\d\-\.]+\.[a-z]{2,3}(:[a-z\d]*)?\/?([a-z\d\-\._\?\,\'\/\\\+&%\$#\=~])*$/i,

    /*RFC 3896 */
    rfc3896url: new RegExp(
        "^" +
        "(?:" +
          "([^:/?#]+)" +         // scheme
        ":)?" +
        "(?://" +
          "(?:([^/?#]*)@)?" +    // credentials
          "([^/?#:@]*)" +        // domain
          "(?::([0-9]+))?" +     // port
        ")?" +
        "([^?#]+)?" +            // path
        "(?:\\?([^#]*))?" +      // query
        "(?:#(.*))?" +           // fragment
        "$"
        ),

    ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,

    date: /^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])(?: ([01]\d|2[0-3])(:[0-5]\d){0,2})?$/,

    time: /^([01]\d|2[0-3])(:[0-5]\d){0,2}$/,

    zipcode: /^\d{6}$/,

    compressed: /.+\.(7z|zip|rar|tar|taz|tgz|gz|gzip)$/i,

    picture: /.+\.(jpe?g|bmp|png|gif|ico|pcx|tif|raw|tga)$/i,

    /*Unicode+Ansi*/
    chinese: /^[\u0391-\uFFE5\u4e00-\u9fa5]+$/,

    phone: /^([(]?0\d{2,3}[)]?[-]?)?\s?\d{4}\-?\d{4}(-\d+)?$/,

    mobile: /^(1[3458])\d{9}$/
  },

  $messages = {
    email: "请输入一个有效的的电子邮件地址",
    url: "请输入一个有效的的URL地址",
    ipv4: "请输入有效的ip地址",
    date: "请输入一个有效的日期",
    time: "请输入一个有效的时间",
    zipcode: "请输入一个有效的邮编",
    compressed: "请选择一个有效的压缩文件",
    picture: "请选择一个有效的图片文件",
    chinese: "请输入中文",
    phone: "请输入一个有效的电话号码",
    mobile: "请输入一个有效的手机号码"
  },

  aCity = { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外" },

  isIDCard = function(sId) {
    var d, iSum = 0;

    if (!/^\d{17}(\d|x)$/i.test(sId))
      return "你输入的身份证长度或格式错误";

    sId = sId.replace(/x$/i, "a");

    if (!aCity[parseInt(sId.substr(0, 2))])
      return "你的身份证地区非法";

    sBirthday = sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2));
    d = new Date(sBirthday.replace(/-/g, "/"));

    if (sBirthday != (d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate()))
      return "身份证上的出生日期非法";

    for (var i = 17;i >= 0;i--)
      iSum += (Math.pow(2, i) % 11) * parseInt(sId.charAt(17 - i), 11);

    if (iSum % 11 != 1)
      return "你输入的身份证号非法";

    return true; //aCity[parseInt(sId.substr(0,2))]+","+sBirthday+","+(sId.substr(16,1)%2?"男":"女")
  };

  $f.addMethod("idcard", function(value, element) {
    return this.optional(element) || isIDCard(value) === true;
  }, "请输入一个有效的身份证号码");

  core.each($patterns, function(re, name) {
    $f.addPattern(name, re, $messages[name]);
  });

  var $ajax = core.ajax = function(url) {
    this.url = url;
    this.xhr = $ajax.create();
  };
  core.extend($ajax, {
    create: function() {
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else if (window.ActiveXObject) {
        return new ActiveXObject("Microsoft.XMLHTTP");
      }
      core.error("XMLHTTP object could be created.");
    },

    param: function(a) {
      var s = [];

      // If an array was passed in, assume that it is an array
      // of form elements
      if (core.isArray(a))
      // Serialize the form elements
        core.each(a, function() {
          s.push(encodeURIComponent(this.name) + "=" + encodeURIComponent(this.value));
        });
      // Otherwise, assume that it's an object of key/value pairs
      else
      // Serialize the key/values
        for (var j in a)
        // If the value is an array then the key names need to be repeated
          if (core.isArray(a[j]))
            core.each(a[j], function() {
              s.push(encodeURIComponent(j) + "=" + encodeURIComponent(this));
            });
          else
            s.push(encodeURIComponent(j) + "=" + encodeURIComponent(a[j]));

      // Return the resulting serialization
      return s.join("&").replace(/%20/g, "+");
    },

    prototype: {
      request: function(option) {
        var data, url = this.url,
          xhr = this.xhr,
          method = option.method || "POST";

        if (this.isrun) {
          this.isrun = false;
          xhr.abort();
        }

        this.isrun = true;
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4) {
            option.callback(xhr.responseText, xhr.status, xhr);
          }
        };

        try {
          data = $ajax.param(option.data);
          xhr.open(method, url, true);
          xhr.setRequestHeader("Content-Length", data.length);
          xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
          xhr.send(data);
        } catch (e) {
          if (option.callback)
            option.callback(xhr.responseText, xhr.status, xhr, e);
        } finally {
          this.isrun = false;
        }
      },

      abort: function() {
        this.xhr && this.xhr.abort();
      }
    }
  });

  var ajaxMessages = {
    ing: "正在请求...",
    failed: "验证失败",
    error: "请求失败"
  },

  ajaxCallback = function(text) {
    return text == 1;
  };

  $f.addMethod("ajax", function(value, element, param) {
    var me = this,
      attr = this.attr(element),
      key = this.getKey(element),
      messages = this.settings.messages[key],
      url = core.isString(param) ? param : param.url,
      callback = param.callback || ajaxCallback,
      data = {};

    if (!messages)
      messages = this.settings.messages[key] = {};

    if (!attr.ajax) {
      attr.ajax = new $ajax(url);
      attr.ajaxMessage = messages["ajax"];
    }

    messages["ajax"] = ajaxMessages.ing;
    data[!core.isString(param) && param.key || key] = value;

    attr.ajax.request({
      data: data,
      callback: function(text, status, xhr, error) {
        var result = status == 200 && callback(text),
          message = (messages["ajax"] = status == 200 ?
            attr.ajaxMessage :
            ajaxMessages.error) ||
            ajaxMessages.failed;

        me.addResult(result, element, message);
        me.hideErrors();
        me.showErrors();
      }
    });
    return this.optional(element) || false;
  }, ajaxMessages.ing);

  function watermark(el, text, useContainer) {
    if (!(this instanceof watermark))
      return new watermark(el, text);

    var place = document.createElement("span");

    if (useContainer) {
      var container = document.createElement("span");
      el.parentNode.insertBefore(container, el);
      container.appendChild(place);
      container.appendChild(el);
      dom.addClass(container, "w-wrapper");
    } else
      el.parentNode.insertBefore(place, el);

    dom.addClass(place, "w-label");
    place.innerHTML = text;
    place.style.height = place.style.lineHeight = el.offsetHeight + "px";

    function hideIfHasValue() {
      if (el.value)
        dom.addClass(place, "w-hide");
    }
    hideIfHasValue();
    event.add(el, "focusin", function() {
      hideIfHasValue()
      if (!el.value)
        dom.addClass(place, "w-active");
    });
    event.add(el, "focusout", function() {
      if (!el.value)
        dom.removeClass(place, "w-active w-hide");
    });
    event.add(el, "keyup", hideIfHasValue);
    event.add(place, "click", function() {
      hideIfHasValue();
      dom.focus(el);
    });
  }

  core.extend(true, $f, {
    watermark: watermark,

    getKeyForAspnet: function(el) {
      if (!el.name) return;
      if (core.inArray("__EVENTTARGET __EVENTARGUMENT __VIEWSTATE __EVENTVALIDATION".split(" "), el.name) > -1) return;
      var clientIds = el.name.split("$"),
        len = clientIds.length,
        last;
      if (len === 1)
        return clientIds[0];
      last = clientIds[len - 1];
      if ($f.core.isDigit(last))
        return clientIds[len - 2];
      return last;
    },

    placeHolder: function(elems) {
      var ph, support = "placeholder" in document.createElement("input");
      if (!support) {
        core.each(elems, function(el) {
          ph = dom.attr(el, "placeholder");
          if (ph) watermark(el, ph);
        });
      }
    },

    bubbleTip: function(label, element) {
      dom.addClass(label, "b-b e-bubble");
      $f.appendContainer.call(this, label, element);
      label.innerHTML = '

';
      label.textNode = label.childNodes[2];
      label.vtarget = element;
    },

    arrowTip: function(label, element) {
      dom.addClass(label, "b-b e-arrow");
      $f.appendContainer.call(this, label, element);
      label.innerHTML = '

';
      label.textNode = label.childNodes[1];
      label.vtarget = element;
    },

    tipText: function(label, message) {
      label.textNode.innerHTML = message;
      $f.appendContainer.call(this, label, label.vtarget);
    },

    prototype: {
      placeHolder: function() {
        $f.placeHolder(this.elements());
      }
    }
  });
})(window.fancy);<commit_msg>Update fancyValidate.additional.js<commit_after>/*!
 * Fancy Validate v0.1.3 - JavaScript Form Validation
 * Copyright 2013 cormin.lu@gmail.com
 * MIT Licensed
 * Build 07/10/2013
 */
(function($f) {
  var core = $f.core,
    dom = $f.dom,
    event = $f.event,

  $patterns = {
    /*RFC 2822 */
    email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,

    url: /^(http|https|ftp)\:\/\/[a-z\d\-\.]+\.[a-z]{2,3}(:[a-z\d]*)?\/?([a-z\d\-\._\?\,\'\/\\\+&%\$#\=~])*$/i,

    /*RFC 3896 */
    rfc3896url: new RegExp(
        "^" +
        "(?:" +
          "([^:/?#]+)" +         // scheme
        ":)?" +
        "(?://" +
          "(?:([^/?#]*)@)?" +    // credentials
          "([^/?#:@]*)" +        // domain
          "(?::([0-9]+))?" +     // port
        ")?" +
        "([^?#]+)?" +            // path
        "(?:\\?([^#]*))?" +      // query
        "(?:#(.*))?" +           // fragment
        "$"
        ),

    ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,

    date: /^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])(?: ([01]\d|2[0-3])(:[0-5]\d){0,2})?$/,

    date2: /^(19|20)\d\d([- /.])(0?[1-9]|1[012])\2(0?[1-9]|[12][0-9]|3[01])(?: ([01]?\d|2[0-3])(:[0-5]?\d){0,2})?$/,

    time: /^([01]\d|2[0-3])(:[0-5]\d){0,2}$/,

    zipcode: /^\d{6}$/,

    compressed: /.+\.(7z|zip|rar|tar|taz|tgz|gz|gzip)$/i,

    picture: /.+\.(jpe?g|bmp|png|gif|ico|pcx|tif|raw|tga)$/i,

    /*Unicode+Ansi*/
    chinese: /^[\u0391-\uFFE5\u4e00-\u9fa5]+$/,

    phone: /^([(]?0\d{2,3}[)]?[-]?)?\s?\d{4}\-?\d{4}(-\d+)?$/,

    mobile: /^(1[3458])\d{9}$/
  },

  $messages = {
    email: "请输入一个有效的的电子邮件地址",
    url: "请输入一个有效的的URL地址",
    ipv4: "请输入有效的ip地址",
    date: "请输入一个有效的日期",
    date2: "请输入一个有效的日期",
    time: "请输入一个有效的时间",
    zipcode: "请输入一个有效的邮编",
    compressed: "请选择一个有效的压缩文件",
    picture: "请选择一个有效的图片文件",
    chinese: "请输入中文",
    phone: "请输入一个有效的电话号码",
    mobile: "请输入一个有效的手机号码"
  },

  aCity = { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外" },

  isIDCard = function(sId) {
    var d, iSum = 0;

    if (!/^\d{17}(\d|x)$/i.test(sId))
      return "你输入的身份证长度或格式错误";

    sId = sId.replace(/x$/i, "a");

    if (!aCity[parseInt(sId.substr(0, 2))])
      return "你的身份证地区非法";

    sBirthday = sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2));
    d = new Date(sBirthday.replace(/-/g, "/"));

    if (sBirthday != (d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate()))
      return "身份证上的出生日期非法";

    for (var i = 17;i >= 0;i--)
      iSum += (Math.pow(2, i) % 11) * parseInt(sId.charAt(17 - i), 11);

    if (iSum % 11 != 1)
      return "你输入的身份证号非法";

    return true; //aCity[parseInt(sId.substr(0,2))]+","+sBirthday+","+(sId.substr(16,1)%2?"男":"女")
  };

  $f.addMethod("idcard", function(value, element) {
    return this.optional(element) || isIDCard(value) === true;
  }, "请输入一个有效的身份证号码");

  core.each($patterns, function(re, name) {
    $f.addPattern(name, re, $messages[name]);
  });

  var $ajax = core.ajax = function(url) {
    this.url = url;
    this.xhr = $ajax.create();
  };
  core.extend($ajax, {
    create: function() {
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else if (window.ActiveXObject) {
        return new ActiveXObject("Microsoft.XMLHTTP");
      }
      core.error("XMLHTTP object could be created.");
    },

    param: function(a) {
      var s = [];

      // If an array was passed in, assume that it is an array
      // of form elements
      if (core.isArray(a))
      // Serialize the form elements
        core.each(a, function() {
          s.push(encodeURIComponent(this.name) + "=" + encodeURIComponent(this.value));
        });
      // Otherwise, assume that it's an object of key/value pairs
      else
      // Serialize the key/values
        for (var j in a)
        // If the value is an array then the key names need to be repeated
          if (core.isArray(a[j]))
            core.each(a[j], function() {
              s.push(encodeURIComponent(j) + "=" + encodeURIComponent(this));
            });
          else
            s.push(encodeURIComponent(j) + "=" + encodeURIComponent(a[j]));

      // Return the resulting serialization
      return s.join("&").replace(/%20/g, "+");
    },

    prototype: {
      request: function(option) {
        var data, url = this.url,
          xhr = this.xhr,
          method = option.method || "POST";

        if (this.isrun) {
          this.isrun = false;
          xhr.abort();
        }

        this.isrun = true;
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4) {
            option.callback(xhr.responseText, xhr.status, xhr);
          }
        };

        try {
          data = $ajax.param(option.data);
          xhr.open(method, url, true);
          xhr.setRequestHeader("Content-Length", data.length);
          xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
          xhr.send(data);
        } catch (e) {
          if (option.callback)
            option.callback(xhr.responseText, xhr.status, xhr, e);
        } finally {
          this.isrun = false;
        }
      },

      abort: function() {
        this.xhr && this.xhr.abort();
      }
    }
  });

  var ajaxMessages = {
    ing: "正在请求...",
    failed: "验证失败",
    error: "请求失败"
  },

  ajaxCallback = function(text) {
    return text == 1;
  };

  $f.addMethod("ajax", function(value, element, param) {
    var me = this,
      attr = this.attr(element),
      key = this.getKey(element),
      messages = this.settings.messages[key],
      url = core.isString(param) ? param : param.url,
      callback = param.callback || ajaxCallback,
      data = {};

    if (!messages)
      messages = this.settings.messages[key] = {};

    if (!attr.ajax) {
      attr.ajax = new $ajax(url);
      attr.ajaxMessage = messages["ajax"];
    }

    messages["ajax"] = ajaxMessages.ing;
    data[!core.isString(param) && param.key || key] = value;

    attr.ajax.request({
      data: data,
      callback: function(text, status, xhr, error) {
        var result = status == 200 && callback(text),
          message = (messages["ajax"] = status == 200 ?
            attr.ajaxMessage :
            ajaxMessages.error) ||
            ajaxMessages.failed;

        me.addResult(result, element, message);
        me.hideErrors();
        me.showErrors();
      }
    });
    return this.optional(element) || false;
  }, ajaxMessages.ing);

  function watermark(el, text, useContainer) {
    if (!(this instanceof watermark))
      return new watermark(el, text);

    var place = document.createElement("span");

    if (useContainer) {
      var container = document.createElement("span");
      el.parentNode.insertBefore(container, el);
      container.appendChild(place);
      container.appendChild(el);
      dom.addClass(container, "w-wrapper");
    } else
      el.parentNode.insertBefore(place, el);

    dom.addClass(place, "w-label");
    place.innerHTML = text;
    place.style.height = place.style.lineHeight = el.offsetHeight + "px";

    function hideIfHasValue() {
      if (el.value)
        dom.addClass(place, "w-hide");
    }
    hideIfHasValue();
    event.add(el, "focusin", function() {
      hideIfHasValue()
      if (!el.value)
        dom.addClass(place, "w-active");
    });
    event.add(el, "focusout", function() {
      if (!el.value)
        dom.removeClass(place, "w-active w-hide");
    });
    event.add(el, "keyup", hideIfHasValue);
    event.add(place, "click", function() {
      hideIfHasValue();
      dom.focus(el);
    });
  }

  core.extend(true, $f, {
    watermark: watermark,

    getKeyForAspnet: function(el) {
      if (!el.name) return;
      if (core.inArray("__EVENTTARGET __EVENTARGUMENT __VIEWSTATE __EVENTVALIDATION".split(" "), el.name) > -1) return;
      var clientIds = el.name.split("$"),
        len = clientIds.length,
        last;
      if (len === 1)
        return clientIds[0];
      last = clientIds[len - 1];
      if ($f.core.isDigit(last))
        return clientIds[len - 2];
      return last;
    },

    placeHolder: function(elems) {
      var ph, support = "placeholder" in document.createElement("input");
      if (!support) {
        core.each(elems, function(el) {
          ph = dom.attr(el, "placeholder");
          if (ph) watermark(el, ph);
        });
      }
    },

    bubbleTip: function(label, element) {
      dom.addClass(label, "b-b e-bubble");
      $f.appendContainer.call(this, label, element);
      label.innerHTML = '

';
      label.textNode = label.childNodes[2];
      label.vtarget = element;
    },

    arrowTip: function(label, element) {
      dom.addClass(label, "b-b e-arrow");
      $f.appendContainer.call(this, label, element);
      label.innerHTML = '

';
      label.textNode = label.childNodes[1];
      label.vtarget = element;
    },

    tipText: function(label, message) {
      label.textNode.innerHTML = message;
      $f.appendContainer.call(this, label, label.vtarget);
    },

    prototype: {
      placeHolder: function() {
        $f.placeHolder(this.elements());
      }
    }
  });
})(window.fancy);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. 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.
 */

/**
  Imports and Exports provide implicit Context dependency management.

  A class can list which values it requires from the Context, and then
  these values will be added to the object itself so that it doesn't need
  to explicitly work with the Context.

  A class can list which values (properties, methods, or method-like axioms)
  that it exports, and these will automatically be added to the object's
  sub-Context. The object's sub-Context is the context that is used when
  new objects are created by the object.

  Ex.

foam.CLASS({
  name: 'ImportsTest',

  imports: [ 'log', 'warn' ],

  methods: [
    function foo() {
      this.log('log foo from ImportTest');
      this.warn('warn foo from ImportTest');
    }
  ]
});

foam.CLASS({
  name: 'ExportsTest',
  requires: [ 'ImportsTest' ],

  exports: [ 'log', 'log as warn' ],

  methods: [
    function init() {
      // ImportsTest will be created in ExportTest's
      // sub-Context, which will have 'log' and 'warn'
      // exported.
      this.ImportsTest.create().foo();
    },
    function log(msg) {
      console.log('log:', msg);
    }
  ]
});

  Aliasing:
    Bindings can be renamed or aliased when they're imported or exported using
    'as alias'.

  Examples:
    // import 'userDAO' from the Context and make available as this.dao
    imports: [ 'userDAO as dao' ]

    // export my log method as 'warn'
    exports: [ 'log as warn' ]

    // If the axiom to be exported isn't named, but just aliased, then 'this'
    // is exported as the named alias.  This is how objects export themselves.
    exports: [ 'as Controller' ]

  See Context.js.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Import',

  documentation: 'Axiom to Import a Context Value.',

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    'key',
    'of',
    {
      name: 'type'
    },
    {
      class: 'Boolean',
      name: 'required',
      value: true
    },
    {
      name: 'slotName_',
      factory: function() {
        return foam.String.toSlotName(this.name);
      }
    }
  ],

  methods: [
    function installInProto(proto) {
      foam.assert(this.key, 'No key for import: ' + this.name);

      var name     = this.name;
      var key      = foam.String.toSlotName(this.key);
      var slotName = this.slotName_;
      var required = this.required;

      Object.defineProperty(proto, slotName, {
        get: function importsSlotGetter() {
          return this.__context__[key];
        },
        configurable: false,
        enumerable: false
      });

      Object.defineProperty(proto, name, {
        get: function importsGetter()  {
          var slot = this[slotName];
          if ( slot ) return slot.get();
          if ( required ) {
            console.warn('Access missing import:', name);
          }
          return undefined;
        },
        set: function importsSetter(v) {
          var slot = this[slotName];
          if ( slot )
            slot.set(v);
          else
            console.warn('Attempt to set missing import:', name);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      return obj[this.slotName_];
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'Export',

  documentation: 'Axiom to Export a Sub-Context Value.',

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      name: 'exportName',
      postSet: function(_, name) {
        this.name = 'export_' + name;
      }
    },
    'key'
  ],

  methods: [
    function getExportMap() {
      var m  = {};
      var bs = this.cls_.getAxiomsByClass(foam.core.Export);
      for ( var i = 0 ; i < bs.length ; i++ ) {
        var b = bs[i];

        if ( b.key ) {
          var path = b.key.split('.');
          var a    = this.cls_.getAxiomByName(path[0]);

          foam.assert(
            a,
            'Unknown axiom: "',
            path[0],
            '" in model: ',
            this.cls_.id,
            ", trying to export: '",
            b.key,
            "'");

          // Axioms have an option of wrapping a value for export.
          // This could be used to bind a method to 'this', for example.
          var e = a.exportAs ? a.exportAs(this, path.slice(1)) : this[path[0]];

          m[b.exportName] = e;
        } else {
          // Ex. 'as Bank', which exports an implicit 'this'
          m[b.exportName] = this;
        }
      }
      return m;
    },

    function installInProto(proto) {
      if ( Object.prototype.hasOwnProperty.call(proto, '__subContext__' ) ) {
        return;
      }

      var axiom = this;

      Object.defineProperty(proto, '__subContext__', {
        get: function YGetter() {
          if ( ! this.hasOwnPrivate_('__subContext__') ) {
            var ctx = this.__context__;
            var m = axiom.getExportMap.call(this);
            this.setPrivate_('__subContext__', ctx.createSubContext(m));
          }

          return this.getPrivate_('__subContext__');
        },
        set: function() {
          throw new Error('Attempted to set unsettable __subContext__ in ' + this.cls_.id);
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',
  package: 'foam.core',
  name: 'ImportExportModelRefine',
  requires: [
    'foam.core.Import',
    'foam.core.Export',
  ],
  properties: [
    {
      class: 'AxiomArray',
      of: 'Import',
      name: 'imports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a        = o.split(' as ');
          var key      = a[0];
          var optional = key.endsWith('?');
          if ( optional ) key = key.slice(0, key.length-1);
          return foam.core.Import.create({name: a[1] || key, key: key, required: ! optional});
        }

        return foam.core.Import.create(o);
      }
    },
    {
      class: 'AxiomArray',
      of: 'Export',
      name: 'exports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a = o.split(' ');

          switch ( a.length ) {
            case 1:
              return foam.core.Export.create({exportName: a[0], key: a[0]});

            case 2:
              // Export 'this'
              foam.assert(
                a[0] === 'as',
                'Invalid export syntax: key [as value] | as value');
              return foam.core.Export.create({exportName: a[1], key: null});

            case 3:
              foam.assert(
                a[1] === 'as',
                'Invalid export syntax: key [as value] | as value');
              return foam.core.Export.create({exportName: a[2], key: a[0]});

            default:
              foam.assert(false,
                'Invalid export syntax: key [as value] | as value');
          }
        }

        return foam.core.Export.create(o);
      }
    }
  ]
});
<commit_msg>Formatting.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. 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.
 */

/**
  Imports and Exports provide implicit Context dependency management.

  A class can list which values it requires from the Context, and then
  these values will be added to the object itself so that it doesn't need
  to explicitly work with the Context.

  A class can list which values (properties, methods, or method-like axioms)
  that it exports, and these will automatically be added to the object's
  sub-Context. The object's sub-Context is the context that is used when
  new objects are created by the object.

  Ex.

foam.CLASS({
  name: 'ImportsTest',

  imports: [ 'log', 'warn' ],

  methods: [
    function foo() {
      this.log('log foo from ImportTest');
      this.warn('warn foo from ImportTest');
    }
  ]
});

foam.CLASS({
  name: 'ExportsTest',
  requires: [ 'ImportsTest' ],

  exports: [ 'log', 'log as warn' ],

  methods: [
    function init() {
      // ImportsTest will be created in ExportTest's
      // sub-Context, which will have 'log' and 'warn'
      // exported.
      this.ImportsTest.create().foo();
    },
    function log(msg) {
      console.log('log:', msg);
    }
  ]
});

  Aliasing:
    Bindings can be renamed or aliased when they're imported or exported using
    'as alias'.

  Examples:
    // import 'userDAO' from the Context and make available as this.dao
    imports: [ 'userDAO as dao' ]

    // export my log method as 'warn'
    exports: [ 'log as warn' ]

    // If the axiom to be exported isn't named, but just aliased, then 'this'
    // is exported as the named alias.  This is how objects export themselves.
    exports: [ 'as Controller' ]

  See Context.js.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Import',

  documentation: 'Axiom to Import a Context Value.',

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    'key',
    'of',
    {
      name: 'type'
    },
    {
      class: 'Boolean',
      name: 'required',
      value: true
    },
    {
      name: 'slotName_',
      factory: function() {
        return foam.String.toSlotName(this.name);
      }
    }
  ],

  methods: [
    function installInProto(proto) {
      foam.assert(this.key, 'No key for import: ' + this.name);

      var name     = this.name;
      var key      = foam.String.toSlotName(this.key);
      var slotName = this.slotName_;
      var required = this.required;

      Object.defineProperty(proto, slotName, {
        get: function importsSlotGetter() {
          return this.__context__[key];
        },
        configurable: false,
        enumerable: false
      });

      Object.defineProperty(proto, name, {
        get: function importsGetter()  {
          var slot = this[slotName];
          if ( slot ) return slot.get();
          if ( required ) {
            console.warn('Access missing import:', name);
          }
          return undefined;
        },
        set: function importsSetter(v) {
          var slot = this[slotName];
          if ( slot )
            slot.set(v);
          else
            console.warn('Attempt to set missing import:', name);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      return obj[this.slotName_];
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'Export',

  documentation: 'Axiom to Export a Sub-Context Value.',

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      name: 'exportName',
      postSet: function(_, name) {
        this.name = 'export_' + name;
      }
    },
    'key'
  ],

  methods: [
    function getExportMap() {
      var m  = {};
      var bs = this.cls_.getAxiomsByClass(foam.core.Export);
      for ( var i = 0 ; i < bs.length ; i++ ) {
        var b = bs[i];

        if ( b.key ) {
          var path = b.key.split('.');
          var a    = this.cls_.getAxiomByName(path[0]);

          foam.assert(
            a,
            'Unknown axiom: "',
            path[0],
            '" in model: ',
            this.cls_.id,
            ", trying to export: '",
            b.key,
            "'");

          // Axioms have an option of wrapping a value for export.
          // This could be used to bind a method to 'this', for example.
          var e = a.exportAs ? a.exportAs(this, path.slice(1)) : this[path[0]];

          m[b.exportName] = e;
        } else {
          // Ex. 'as Bank', which exports an implicit 'this'
          m[b.exportName] = this;
        }
      }
      return m;
    },

    function installInProto(proto) {
      if ( Object.prototype.hasOwnProperty.call(proto, '__subContext__' ) ) {
        return;
      }

      var axiom = this;

      Object.defineProperty(proto, '__subContext__', {
        get: function YGetter() {
          if ( ! this.hasOwnPrivate_('__subContext__') ) {
            var ctx = this.__context__;
            var m = axiom.getExportMap.call(this);
            this.setPrivate_('__subContext__', ctx.createSubContext(m));
          }

          return this.getPrivate_('__subContext__');
        },
        set: function() {
          throw new Error('Attempted to set unsettable __subContext__ in ' + this.cls_.id);
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',
  package: 'foam.core',
  name: 'ImportExportModelRefine',

  requires: [
    'foam.core.Import',
    'foam.core.Export',
  ],

  properties: [
    {
      class: 'AxiomArray',
      of: 'Import',
      name: 'imports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a        = o.split(' as ');
          var key      = a[0];
          var optional = key.endsWith('?');
          if ( optional ) key = key.slice(0, key.length-1);
          return foam.core.Import.create({name: a[1] || key, key: key, required: ! optional});
        }

        return foam.core.Import.create(o);
      }
    },
    {
      class: 'AxiomArray',
      of: 'Export',
      name: 'exports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a = o.split(' ');

          switch ( a.length ) {
            case 1:
              return foam.core.Export.create({exportName: a[0], key: a[0]});

            case 2:
              // Export 'this'
              foam.assert(
                a[0] === 'as',
                'Invalid export syntax: key [as value] | as value');
              return foam.core.Export.create({exportName: a[1], key: null});

            case 3:
              foam.assert(
                a[1] === 'as',
                'Invalid export syntax: key [as value] | as value');
              return foam.core.Export.create({exportName: a[2], key: a[0]});

            default:
              foam.assert(false,
                'Invalid export syntax: key [as value] | as value');
          }
        }

        return foam.core.Export.create(o);
      }
    }
  ]
});
<|endoftext|>


<commit_before>/* eslint-disable max-lines-per-function */

/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

// @ts-check

/**
 * @typedef {import('playwright').Page} Page
 */

const
	h = include('tests/helpers');

/**
 * Starts a test
 *
 * @param {Page} page
 * @param {!Object} params
 * @returns {void}
 */
module.exports = (page, params) => {
	const initialUrl = page.url();

	describe('b-button', () => {
		beforeEach(async () => {
			page = await params.context.newPage();
			await page.goto(initialUrl);
		});

		afterEach(() => page.close());

		let
			buttonNode,
			buttonCtx;

		beforeEach(async () => {
			await page.evaluate(() => {
				globalThis._t = undefined;
				globalThis.removeCreatedComponents();
			});
		});

		describe('providing `type`', () => {
			describe('`file`', () => {
				beforeEach(async () => {
					await renderButton({
						type: 'file'
					});
				});

				it('renders a button with a `file` type', async () => {
					const
						fileEl = await buttonNode.$('[type="file"]');

					expect(fileEl).toBeTruthy();
				});

				it('opens a file chooser on click', async () => {
					const
						event = page.waitForEvent('filechooser');

					await buttonNode.click();
					await expectAsync(event).toBeResolved();
				});

				describe('file chooser has been opened, and the file has been selected', () => {
					let changeEventPr;

					beforeEach(async () => {
						const
							fileChooserPr = page.waitForEvent('filechooser');

						changeEventPr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('change'));

						await buttonNode.click();

						const
							fileChooser = await fileChooserPr;

						await fileChooser.setFiles({
							name: 'somefile.pdf',
							mimeType: 'application/pdf',
							buffer: Buffer.from([])
						});
					});

					it('stores the provided file', async () => {
						const
							filesLength = await buttonCtx.evaluate((ctx) => ctx.files.length);

						expect(filesLength).toBe(1);
					});

					it('fires a `change` event', async () => {
						await expectAsync(changeEventPr).toBeResolved();
					});

					it('resets the provided files on the `reset` method call', async () => {
						await buttonCtx.evaluate((ctx) => ctx.reset());

						const
							filesLength = await buttonCtx.evaluate((ctx) => ctx.files.length);

						expect(filesLength).toBe(0);
					});
				});
			});

			describe('`submit`', () => {
				beforeEach(async () => {
					await renderButton({
						type: 'submit'
					});
				});

				it('renders a button with a `submit` type', async () => {
					const
						submitEl = await buttonNode.$('[type="submit"]');

					expect(submitEl).toBeTruthy();
				});

				it('fires a `submit` event on click', async () => {
					const pr = page.evaluate(() => new Promise((res) => {
						const
							form = document.createElement('form');

						document.body.prepend(form);
						form.appendChild(globalThis.buttonNode);
						form.onsubmit = (e) => {
							e.preventDefault();
							res();
						};
					}));

					await buttonNode.click();

					await expectAsync(pr).toBeResolved();
				});
			});

			describe('`link`', () => {
				const
					url = 'https://someurl.com/';

				beforeEach(async () => {
					await renderButton({
						href: url,
						type: 'link'
					});
				});

				it('renders a button with the provided `href`', async () => {
					const
						linkEl = await buttonNode.$(`[href="${url}"]`);

					expect(linkEl).toBeTruthy();
				});

				it('navigates to the provided `href`', async () => {
					const pr = new Promise(async (res) => {
						await page.route('**/*', (r) => {
							if (r.request().isNavigationRequest()) {
								res(r.request().url());
								return r.fulfill({
									status: 200
								});
							}

							return r.continue();
						});
					});

					await buttonNode.click();

					const
						navigationUrl = await pr;

					expect(navigationUrl).toBe(url);
				});
			});

		});

		describe('`href`', () => {
			it('provides a base URL to the `dataProvider`', async () => {
				const pr = new Promise(async (res) => {
					await page.route('**/api/test/base', (r) => {
						res();
						return r.fulfill({
							status: 200,
							contentType: 'application/json',
							body: JSON.stringify({})
						});
					});
				});

				await renderButton({
					dataProvider: 'demo.List',
					href: 'test/base',
					defaultRequestFilter: false
				});

				await buttonNode.click();

				await expectAsync(pr).toBeResolved();
			});
		});

		describe('providing `disabled`', () => {
			describe('`true`', () => {
				it('does not fire any click event', async () => {
					await renderButton({
						disabled: true
					});

					await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
					await buttonNode.click();
					await h.bom.waitForIdleCallback(page);

					const
						testVal = await page.evaluate(() => globalThis._t);

					expect(testVal).toBeUndefined();
				});

				it('does not fire a navigation', async () => {
					await renderButton({
						disabled: true,
						type: 'link',
						href: 'https://someurl.com/'
					});

					let hasNavRequest = false;

					await page.route('**/*', (r) => {
						if (r.request().isNavigationRequest()) {
							hasNavRequest = true;
						}

						return r.continue();
					});

					await buttonNode.click();
					await h.bom.waitForIdleCallback(page, {sleepAfterIdles: 300});

					expect(hasNavRequest).toBeFalse();
				});
			});

			describe('`false`', () => {
				it('fires a `click` event', async () => {
					await renderButton({
						disabled: false
					});

					await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
					await buttonNode.click();
					await h.bom.waitForIdleCallback(page);

					const
						testVal = await page.evaluate(() => globalThis._t);

					expect(testVal).toBe(1);
				});
			});
		});

		describe('providing `autofocus`', () => {
			it('`true`', async () => {
				await renderButton({
					autofocus: true
				});

				const
					autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

				expect(autofocusEl).toBeTruthy();
			});

			it('`false`', async () => {
				await renderButton({
					autofocus: false
				});

				const
					autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

				expect(autofocusEl).toBeNull();
			});
		});

		describe('providing `tabIndex`', () => {
			it('`-1`', async () => {
				await renderButton({
					tabIndex: -1
				});

				const
					tabIndexEl = await buttonNode.$('[tabindex="-1"]');

				expect(tabIndexEl).toBeTruthy();
			});

			it('`1`', async () => {
				await renderButton({
					tabIndex: 1
				});

				const
					tabIndexEl = await buttonNode.$('[tabindex="1"]');

				expect(tabIndexEl).toBeTruthy();
			});
		});

		describe('providing `preIcon`', () => {
			it('`dropdown`', async () => {
				const
					iconName = 'foo';

				await renderButton({
					preIcon: iconName
				});

				const
					bIcon = await buttonNode.$('.b-icon'),
					iconProvidedName = await bIcon.evaluate((ctx) => ctx.component.value);

				expect(iconProvidedName).toBe(iconName);
			});

			it('`undefined`', async () => {
				await renderButton({
					preIcon: undefined
				});

				const
					bIcon = await buttonNode.$('.b-icon');

				expect(bIcon).toBeNull();
			});
		});

		describe('click event', () => {
			beforeEach(async () => {
				await renderButton();
			});

			it('fires on click', async () => {
				const
					pr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('click'));

				await buttonNode.click();

				await expectAsync(pr).toBeResolved();
			});

			it('does not emit an event without a click', async () => {
				await buttonCtx.evaluate((ctx) => ctx.once('click', () => globalThis._t = 1));
				await h.bom.waitForIdleCallback(page, {sleepAfterIdles: 400});

				const
					res = await page.evaluate(() => globalThis._t);

				expect(res).toBeUndefined();
			});
		});

		async function renderButton(props = {}) {
			await page.evaluate((props) => {
				const scheme = [
					{
						attrs: {
							id: 'target',
							...props
						},

						content: {
							default: () => 'Hello there general kenobi'
						}
					}
				];

				globalThis.renderComponents('b-button', scheme);

				globalThis.buttonNode = document.getElementById('target');
				globalThis.buttonCtx = globalThis.buttonNode.component;

			}, props);

			buttonNode = await page.waitForSelector('#target');
			buttonCtx = await h.component.getComponentById(page, 'target');
		}
	});
};
<commit_msg>Fix for flappy test<commit_after>/* eslint-disable max-lines-per-function */

/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

// @ts-check

/**
 * @typedef {import('playwright').Page} Page
 */

const
	h = include('tests/helpers');

/**
 * Starts a test
 *
 * @param {Page} page
 * @param {!Object} params
 * @returns {void}
 */
module.exports = (page, params) => {
	const initialUrl = page.url();

	describe('b-button', () => {
		beforeEach(async () => {
			page = await params.context.newPage();
			await page.goto(initialUrl);
		});

		afterEach(() => page.close());

		let
			buttonNode,
			buttonCtx;

		beforeEach(async () => {
			await page.evaluate(() => {
				globalThis._t = undefined;
				globalThis.removeCreatedComponents();
			});
		});

		describe('providing `type`', () => {
			describe('`file`', () => {
				beforeEach(async () => {
					await renderButton({
						type: 'file'
					});
				});

				it('renders a button with a `file` type', async () => {
					const
						fileEl = await buttonNode.$('[type="file"]');

					expect(fileEl).toBeTruthy();
				});

				it('opens a file chooser on click', async () => {
					const
						event = page.waitForEvent('filechooser');

					await buttonNode.click();
					await expectAsync(event).toBeResolved();
				});

				describe('file chooser has been opened, and the file has been selected', () => {
					let changeEventPr;

					beforeEach(async () => {
						const
							fileChooserPr = page.waitForEvent('filechooser');

						changeEventPr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('change'));

						await buttonNode.click();

						const
							fileChooser = await fileChooserPr;

						await fileChooser.setFiles({
							name: 'somefile.pdf',
							mimeType: 'application/pdf',
							buffer: Buffer.from([])
						});
					});

					it('stores the provided file', async () => {
						const
							filesLength = await buttonCtx.evaluate((ctx) => ctx.files.length);

						expect(filesLength).toBe(1);
					});

					it('fires a `change` event', async () => {
						await expectAsync(changeEventPr).toBeResolved();
					});

					it('resets the provided files on the `reset` method call', async () => {
						await buttonCtx.evaluate((ctx) => ctx.reset());

						const
							filesLength = await buttonCtx.evaluate((ctx) => ctx.files.length);

						expect(filesLength).toBe(0);
					});
				});
			});

			describe('`submit`', () => {
				beforeEach(async () => {
					await renderButton({
						type: 'submit'
					});
				});

				it('renders a button with a `submit` type', async () => {
					const
						submitEl = await buttonNode.$('[type="submit"]');

					expect(submitEl).toBeTruthy();
				});

				it('fires a `submit` event on click', async () => {
					const pr = page.evaluate(() => new Promise((res) => {
						const
							form = document.createElement('form');

						document.body.prepend(form);
						form.appendChild(globalThis.buttonNode);
						form.onsubmit = (e) => {
							e.preventDefault();
							res();
						};
					}));

					await buttonNode.click();

					await expectAsync(pr).toBeResolved();
				});
			});

			describe('`link`', () => {
				const
					url = 'https://someurl.com/';

				beforeEach(async () => {
					await renderButton({
						href: url,
						type: 'link'
					});
				});

				it('renders a button with the provided `href`', async () => {
					const
						linkEl = await buttonNode.$(`[href="${url}"]`);

					expect(linkEl).toBeTruthy();
				});

				it('navigates to the provided `href`', async () => {
					const pr = new Promise(async (res) => {
						await page.route('**/*', (r) => {
							if (r.request().isNavigationRequest()) {
								res(r.request().url());
								return r.fulfill({
									status: 200
								});
							}

							return r.continue();
						});
					});

					await buttonNode.click();

					const
						navigationUrl = await pr;

					expect(navigationUrl).toBe(url);
				});
			});

		});

		describe('`href`', () => {
			it('provides a base URL to the `dataProvider`', async () => {
				const pr = new Promise(async (res) => {
					await page.route('**/api/test/base', (r) => {
						res();
						return r.fulfill({
							status: 200,
							contentType: 'application/json',
							body: JSON.stringify({})
						});
					});
				});

				await renderButton({
					dataProvider: 'demo.List',
					href: 'test/base',
					defaultRequestFilter: false
				});

				await h.bom.waitForIdleCallback(page);
				await buttonNode.click();

				await expectAsync(pr).toBeResolved();
			});
		});

		describe('providing `disabled`', () => {
			describe('`true`', () => {
				it('does not fire any click event', async () => {
					await renderButton({
						disabled: true
					});

					await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
					await buttonNode.click();
					await h.bom.waitForIdleCallback(page);

					const
						testVal = await page.evaluate(() => globalThis._t);

					expect(testVal).toBeUndefined();
				});

				it('does not fire a navigation', async () => {
					await renderButton({
						disabled: true,
						type: 'link',
						href: 'https://someurl.com/'
					});

					let hasNavRequest = false;

					await page.route('**/*', (r) => {
						if (r.request().isNavigationRequest()) {
							hasNavRequest = true;
						}

						return r.continue();
					});

					await buttonNode.click();
					await h.bom.waitForIdleCallback(page, {sleepAfterIdles: 300});

					expect(hasNavRequest).toBeFalse();
				});
			});

			describe('`false`', () => {
				it('fires a `click` event', async () => {
					await renderButton({
						disabled: false
					});

					await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
					await buttonNode.click();
					await h.bom.waitForIdleCallback(page);

					const
						testVal = await page.evaluate(() => globalThis._t);

					expect(testVal).toBe(1);
				});
			});
		});

		describe('providing `autofocus`', () => {
			it('`true`', async () => {
				await renderButton({
					autofocus: true
				});

				const
					autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

				expect(autofocusEl).toBeTruthy();
			});

			it('`false`', async () => {
				await renderButton({
					autofocus: false
				});

				const
					autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

				expect(autofocusEl).toBeNull();
			});
		});

		describe('providing `tabIndex`', () => {
			it('`-1`', async () => {
				await renderButton({
					tabIndex: -1
				});

				const
					tabIndexEl = await buttonNode.$('[tabindex="-1"]');

				expect(tabIndexEl).toBeTruthy();
			});

			it('`1`', async () => {
				await renderButton({
					tabIndex: 1
				});

				const
					tabIndexEl = await buttonNode.$('[tabindex="1"]');

				expect(tabIndexEl).toBeTruthy();
			});
		});

		describe('providing `preIcon`', () => {
			it('`dropdown`', async () => {
				const
					iconName = 'foo';

				await renderButton({
					preIcon: iconName
				});

				const
					bIcon = await buttonNode.$('.b-icon'),
					iconProvidedName = await bIcon.evaluate((ctx) => ctx.component.value);

				expect(iconProvidedName).toBe(iconName);
			});

			it('`undefined`', async () => {
				await renderButton({
					preIcon: undefined
				});

				const
					bIcon = await buttonNode.$('.b-icon');

				expect(bIcon).toBeNull();
			});
		});

		describe('click event', () => {
			beforeEach(async () => {
				await renderButton();
			});

			it('fires on click', async () => {
				const
					pr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('click'));

				await buttonNode.click();

				await expectAsync(pr).toBeResolved();
			});

			it('does not emit an event without a click', async () => {
				await buttonCtx.evaluate((ctx) => ctx.once('click', () => globalThis._t = 1));
				await h.bom.waitForIdleCallback(page, {sleepAfterIdles: 400});

				const
					res = await page.evaluate(() => globalThis._t);

				expect(res).toBeUndefined();
			});
		});

		async function renderButton(props = {}) {
			await page.evaluate((props) => {
				const scheme = [
					{
						attrs: {
							id: 'target',
							...props
						},

						content: {
							default: () => 'Hello there general kenobi'
						}
					}
				];

				globalThis.renderComponents('b-button', scheme);

				globalThis.buttonNode = document.getElementById('target');
				globalThis.buttonCtx = globalThis.buttonNode.component;

			}, props);

			buttonNode = await page.waitForSelector('#target');
			buttonCtx = await h.component.getComponentById(page, 'target');
		}
	});
};
<|endoftext|>


<commit_before>import fetchMock from 'fetch-mock';

import { mockNormandy } from '../../actions/tests/utils.js';
import {
  classifyClient,
  doesRecipeMatch,
  fetchAction,
  fetchRecipes,
  filterContext,
  loadActionImplementation,
} from '../self_repair_runner.js';
import { urlPathMatcher } from '../../tests/utils.js';

const UUID_ISH_REGEX = /^[a-f0-9-]{36}$/;

describe('Self-Repair Runner', () => {
  afterEach(() => {
    expect(fetchMock.calls().unmatched).toEqual([]);
    fetchMock.restore();
  });

  describe('classifyClient', () => {
    const url = '/api/v1/classify/';
    const responseData = {
      request_time: '2016-01-01',
      country: 'US',
    };

    beforeEach(() => {
      document.documentElement.dataset.classifyUrl = url;
      fetchMock.get(urlPathMatcher(url), responseData);
    });

    it('should make a request to the normandy server', async () => {
      expect(await classifyClient()).toEqual(jasmine.objectContaining({
        request_time: new Date(responseData.request_time),
        country: 'US',
      }));
      expect(new URL(fetchMock.lastUrl()).pathname).toEqual(url);
    });
  });

  describe('fetchRecipes', () => {
    it('should request recipes from server', async () => {
      document.documentElement.dataset.recipeUrl = '/api/v1/recipe/';
      fetchMock.get('/api/v1/recipe/?enabled=true', []);

      await fetchRecipes();

      expect(fetchMock.lastOptions()).toEqual({
        headers: {
          Accept: 'application/json',
        },
      });
    });
  });

  describe('filterContext', () => {
    it('should contain a valid user ID', async () => {
      document.documentElement.dataset.classifyUrl = '/api/v1/classify/';
      fetchMock.get(urlPathMatcher('/api/v1/classify/'), {
        request_time: '2016-01-01',
        country: 'US',
      });

      const driver = mockNormandy();
      const context = await filterContext(driver);
      expect(context.normandy.userId).toBeDefined();
      expect(context.normandy.userId).toMatch(UUID_ISH_REGEX);
    });
  });

  describe('doesRecipeMatch', () => {
    it('should include the recipe in the filter expression context', async () => {
      const recipe = {
        filter_expression: 'recipe.shouldPass',
        shouldPass: true,
      };

      let match = await doesRecipeMatch(recipe, {});
      expect(match[1]).toBe(true);

      // If shouldPass changes, so should the filter expression's result
      recipe.shouldPass = false;
      match = await doesRecipeMatch(recipe, {});
      expect(match[1]).toBe(false);
    });
  });

  describe('fetchAction', () => {
    it('should request actions from server', async () => {
      const recipe = { action: 'test' };
      fetchMock.get(`/api/v1/action/${recipe.action}/`, {});
      await fetchAction(recipe);

      expect(fetchMock.lastOptions()).toEqual({
        headers: {
          Accept: 'application/json',
        },
      });
    });

    it('should not make more than one request for the same action', async () => {
      const recipe = { action: 'test' };

      // Mock fetch that returns an action with an increasing number. If we
      // make more than one request, then fetchAction will return different
      // values for the same recipe.
      let id = 0;
      fetchMock.get(`/api/v1/action/${recipe.action}/`, () => {
        id++;
        return `{"mock": "action", "id": ${id}}`;
      });

      const action1Promise = fetchAction(recipe);
      const action2Promise = fetchAction(recipe);
      const action1 = await action1Promise;
      const action2 = await action2Promise;
      expect(action1).toEqual(action2);
    });
  });

  describe('loadActionImplementation', async () => {
    it('should not make more than one request for the same action', async () => {
      window.loadActionImplementationTest = 0;
      const url = (
        'data:text/javascript,registerAction("test", window.loadActionImplementationTest++)'
      );
      const action = { name: 'test', implementation_url: url };

      const impl1Promise = loadActionImplementation(action);
      const impl2Promise = loadActionImplementation(action);
      const impl1 = await impl1Promise;
      const impl2 = await impl2Promise;
      expect(impl1).toEqual(impl2);
    });
  });
});
<commit_msg>Fix control tests<commit_after>import fetchMock from 'fetch-mock';

import { mockNormandy } from '../../actions/tests/utils.js';
import {
  classifyClient,
  doesRecipeMatch,
  fetchAction,
  fetchRecipes,
  filterContext,
  loadActionImplementation,
} from '../self_repair_runner.js';
import { urlPathMatcher } from '../../tests/utils.js';

const UUID_ISH_REGEX = /^[a-f0-9-]{36}$/;

describe('Self-Repair Runner', () => {
  afterEach(() => {
    expect(fetchMock.calls().unmatched).toEqual([]);
    fetchMock.restore();
  });

  describe('classifyClient', () => {
    const url = '/api/v1/classify/';
    const responseData = {
      request_time: '2016-01-01',
      country: 'US',
    };

    beforeEach(() => {
      document.documentElement.dataset.classifyUrl = url;
      fetchMock.get(urlPathMatcher(url), responseData);
    });

    it('should make a request to the normandy server', async () => {
      expect(await classifyClient()).toEqual(jasmine.objectContaining({
        request_time: new Date(responseData.request_time),
        country: 'US',
      }));
      expect(new URL(fetchMock.lastUrl()).pathname).toEqual(url);
    });
  });

  describe('fetchRecipes', () => {
    it('should request recipes from server', async () => {
      document.documentElement.dataset.recipeUrl = '/api/v1/recipe/';
      fetchMock.get('/api/v1/recipe/?enabled=true', []);

      await fetchRecipes();

      expect(fetchMock.lastOptions()).toEqual({
        credentials: 'same-origin',
        headers: {
          Accept: 'application/json',
        },
      });
    });
  });

  describe('filterContext', () => {
    it('should contain a valid user ID', async () => {
      document.documentElement.dataset.classifyUrl = '/api/v1/classify/';
      fetchMock.get(urlPathMatcher('/api/v1/classify/'), {
        request_time: '2016-01-01',
        country: 'US',
      });

      const driver = mockNormandy();
      const context = await filterContext(driver);
      expect(context.normandy.userId).toBeDefined();
      expect(context.normandy.userId).toMatch(UUID_ISH_REGEX);
    });
  });

  describe('doesRecipeMatch', () => {
    it('should include the recipe in the filter expression context', async () => {
      const recipe = {
        filter_expression: 'recipe.shouldPass',
        shouldPass: true,
      };

      let match = await doesRecipeMatch(recipe, {});
      expect(match[1]).toBe(true);

      // If shouldPass changes, so should the filter expression's result
      recipe.shouldPass = false;
      match = await doesRecipeMatch(recipe, {});
      expect(match[1]).toBe(false);
    });
  });

  describe('fetchAction', () => {
    it('should request actions from server', async () => {
      const recipe = { action: 'test' };
      fetchMock.get(`/api/v1/action/${recipe.action}/`, {});
      await fetchAction(recipe);

      expect(fetchMock.lastOptions()).toEqual({
        credentials: 'same-origin',
        headers: {
          Accept: 'application/json',
        },
      });
    });

    it('should not make more than one request for the same action', async () => {
      const recipe = { action: 'test' };

      // Mock fetch that returns an action with an increasing number. If we
      // make more than one request, then fetchAction will return different
      // values for the same recipe.
      let id = 0;
      fetchMock.get(`/api/v1/action/${recipe.action}/`, () => {
        id++;
        return `{"mock": "action", "id": ${id}}`;
      });

      const action1Promise = fetchAction(recipe);
      const action2Promise = fetchAction(recipe);
      const action1 = await action1Promise;
      const action2 = await action2Promise;
      expect(action1).toEqual(action2);
    });
  });

  describe('loadActionImplementation', async () => {
    it('should not make more than one request for the same action', async () => {
      window.loadActionImplementationTest = 0;
      const url = (
        'data:text/javascript,registerAction("test", window.loadActionImplementationTest++)'
      );
      const action = { name: 'test', implementation_url: url };

      const impl1Promise = loadActionImplementation(action);
      const impl2Promise = loadActionImplementation(action);
      const impl1 = await impl1Promise;
      const impl2 = await impl2Promise;
      expect(impl1).toEqual(impl2);
    });
  });
});
<|endoftext|>


<commit_before>
/*
  Provides a Javascript API to create, update and lookup functions.

  Think of this as an in-memory function library. It does not parse or
  run functions only

  It is used by the cell engine to lookup function calls, pick the best
  available implementation and run it.
*/

export default class FunctionManager {

  constructor() {
    this.functions = {}
  }

  /*
    Create a function record with a given signature

    fm.createFunction('sum,' {
      params: [
        { name: 'value1', type: 'number', description: 'The first number or range to add together.' },
        { name: 'value2', type: 'number', repeatable: true, description: 'Additional numbers or ranges to add to `value1`.' }
      ],
      returns: { type: 'number', description: 'The sum of a series of numbers and/or cells.'}
    })
  */
  createFunction(name, signature) {
    // NOTE: function has no implementation at first
    this.functions[name] = {
      signature,
      implementations: {}
    }
  }

  /*
    Adds a new implementation to a function. An existing implementation
    will be overridden.

    fm.addImplementation('sum', 'javascript', 'return value1 + value2')
  */
  addImplementation(funcName, language, sourceCode) {
    this.functions[funcName].implementations[language] = sourceCode
  }

  removeImplementation(funcName, language) {
    delete this.functions[funcName].implementations[language]
  }

  /*
    Import and register function based on the given XML serialized version. This can
    be used for seeding the core function library.
  */
  importFunction(name, xmlString) {
    // TODO: import from xmlString
  }

  exportFunction(name) {
    // TODO: serialize as XML and return as string
  }

  /*
    Get the function signature for inspection
  */
  getSignature(name) {
    return this.functions[name].signature
  }

  /*
    fm.getImplementation('sum', 'javascript', ['number', 'number'])
  */
  getImplementation(name, language) {
    let { implementations } = this.functions[name]
    return implementations[language]
  }

}
<commit_msg>Comments.<commit_after>
/*
  Provides a Javascript API to create, update and lookup functions.

  Think of this as an in-memory function library. It does not parse or
  run functions, only the data is stored here for reflection.

  FunctionManager is used by the cell engine to lookup function calls, pick the
  right implementation and runs it.
*/
export default class FunctionManager {

  constructor() {
    this.functions = {}
  }

  /*
    Create a function record with a given signature

    fm.createFunction('sum,' {
      params: [
        { name: 'value1', type: 'number', description: 'The first number or range to add together.' },
        { name: 'value2', type: 'number', repeatable: true, description: 'Additional numbers or ranges to add to `value1`.' }
      ],
      returns: { type: 'number', description: 'The sum of a series of numbers and/or cells.'}
    })
  */
  createFunction(name, signature) {
    // NOTE: function has no implementation at first
    this.functions[name] = {
      signature,
      implementations: {}
    }
  }

  /*
    Adds a new implementation to a function. An existing implementation
    will be overridden.

    fm.addImplementation('sum', 'javascript', 'return value1 + value2')
  */
  addImplementation(funcName, language, sourceCode) {
    this.functions[funcName].implementations[language] = sourceCode
  }

  removeImplementation(funcName, language) {
    delete this.functions[funcName].implementations[language]
  }

  /*
    Import and register function based on the given XML serialized version. This can
    be used for seeding the core function library.
  */
  importFunction(name, xmlString) {
    // TODO: import from xmlString
  }

  exportFunction(name) {
    // TODO: serialize as XML and return as string
  }

  /*
    Get the function signature for inspection
  */
  getSignature(name) {
    return this.functions[name].signature
  }

  /*
    fm.getImplementation('sum', 'javascript', ['number', 'number'])
  */
  getImplementation(name, language) {
    let { implementations } = this.functions[name]
    return implementations[language]
  }

}
<|endoftext|>


<commit_before>import { Float32BufferAttribute } from '../core/BufferAttribute.js';
import { BufferGeometry } from '../core/BufferGeometry.js';
import { Vector3 } from '../math/Vector3.js';
import { Vector2 } from '../math/Vector2.js';
import * as MathUtils from '../math/MathUtils.js';

class LatheGeometry extends BufferGeometry {

	constructor( points = [ new Vector2( 0, 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, - 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) {

		super();

		this.type = 'LatheGeometry';

		this.parameters = {
			points: points,
			segments: segments,
			phiStart: phiStart,
			phiLength: phiLength
		};

		segments = Math.floor( segments );

		// clamp phiLength so it's in range of [ 0, 2PI ]

		phiLength = MathUtils.clamp( phiLength, 0, Math.PI * 2 );

		// buffers

		const indices = [];
		const vertices = [];
		const uvs = [];
		const initNormals = [];
		const normals = [];

		// helper variables

		const inverseSegments = 1.0 / segments;
		const vertex = new Vector3();
		const uv = new Vector2();
		const normal = new Vector3();
		const curNormal = new Vector3();
		const prevNormal = new Vector3();
		let dx = 0;
		let dy = 0;

		// pre-compute normals for initial "meridian"

		for ( let j = 0; j <= ( points.length - 1 ); j ++ ) {

			switch ( j ) {

				case 0:				// special handling for 1st vertex on path

					dx = points[ j + 1 ].x - points[ j ].x;
					dy = points[ j + 1 ].y - points[ j ].y;

					normal.x = dy * 1.0;
					normal.y = - dx;
					normal.z = dy * 0.0;

					prevNormal.copy( normal );

					normal.normalize();

					initNormals.push( normal.x, normal.y, normal.z );

					break;

				case ( points.length - 1 ):	// special handling for last Vertex on path

					initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z );

					break;

				default:			// default handling for all vertices in between

					dx = points[ j + 1 ].x - points[ j ].x;
					dy = points[ j + 1 ].y - points[ j ].y;

					normal.x = dy * 1.0;
					normal.y = - dx;
					normal.z = dy * 0.0;

					curNormal.copy( normal );

					normal.x += prevNormal.x;
					normal.y += prevNormal.y;
					normal.z += prevNormal.z;

					normal.normalize();

					initNormals.push( normal.x, normal.y, normal.z );

					prevNormal.copy( curNormal );

			}

		}

		// generate vertices, uvs and normals

		for ( let i = 0; i <= segments; i ++ ) {

			const phi = phiStart + i * inverseSegments * phiLength;

			const sin = Math.sin( phi );
			const cos = Math.cos( phi );

			for ( let j = 0; j <= ( points.length - 1 ); j ++ ) {

				// vertex

				vertex.x = points[ j ].x * sin;
				vertex.y = points[ j ].y;
				vertex.z = points[ j ].x * cos;

				vertices.push( vertex.x, vertex.y, vertex.z );

				// uv

				uv.x = i / segments;
				uv.y = j / ( points.length - 1 );

				uvs.push( uv.x, uv.y );

				// normal

				const x = initNormals[ 3 * j + 0 ] * sin;
				const y = initNormals[ 3 * j + 1 ];
				const z = initNormals[ 3 * j + 0 ] * cos;

				normals.push( x, y, z );

			}

		}

		// indices

		for ( let i = 0; i < segments; i ++ ) {

			for ( let j = 0; j < ( points.length - 1 ); j ++ ) {

				const base = j + i * points.length;

				const a = base;
				const b = base + points.length;
				const c = base + points.length + 1;
				const d = base + 1;

				// faces

				indices.push( a, b, d );
				indices.push( b, c, d );

			}

		}

		// build geometry

		this.setIndex( indices );
		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
		this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );

	}

	static fromJSON( data ) {

		return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength );

	}

}

export { LatheGeometry, LatheGeometry as LatheBufferGeometry };
<commit_msg>Change Index Generation in LatheGeometry() (#23291)<commit_after>import { Float32BufferAttribute } from '../core/BufferAttribute.js';
import { BufferGeometry } from '../core/BufferGeometry.js';
import { Vector3 } from '../math/Vector3.js';
import { Vector2 } from '../math/Vector2.js';
import * as MathUtils from '../math/MathUtils.js';

class LatheGeometry extends BufferGeometry {

	constructor( points = [ new Vector2( 0, 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, - 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) {

		super();

		this.type = 'LatheGeometry';

		this.parameters = {
			points: points,
			segments: segments,
			phiStart: phiStart,
			phiLength: phiLength
		};

		segments = Math.floor( segments );

		// clamp phiLength so it's in range of [ 0, 2PI ]

		phiLength = MathUtils.clamp( phiLength, 0, Math.PI * 2 );

		// buffers

		const indices = [];
		const vertices = [];
		const uvs = [];
		const initNormals = [];
		const normals = [];

		// helper variables

		const inverseSegments = 1.0 / segments;
		const vertex = new Vector3();
		const uv = new Vector2();
		const normal = new Vector3();
		const curNormal = new Vector3();
		const prevNormal = new Vector3();
		let dx = 0;
		let dy = 0;

		// pre-compute normals for initial "meridian"

		for ( let j = 0; j <= ( points.length - 1 ); j ++ ) {

			switch ( j ) {

				case 0:				// special handling for 1st vertex on path

					dx = points[ j + 1 ].x - points[ j ].x;
					dy = points[ j + 1 ].y - points[ j ].y;

					normal.x = dy * 1.0;
					normal.y = - dx;
					normal.z = dy * 0.0;

					prevNormal.copy( normal );

					normal.normalize();

					initNormals.push( normal.x, normal.y, normal.z );

					break;

				case ( points.length - 1 ):	// special handling for last Vertex on path

					initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z );

					break;

				default:			// default handling for all vertices in between

					dx = points[ j + 1 ].x - points[ j ].x;
					dy = points[ j + 1 ].y - points[ j ].y;

					normal.x = dy * 1.0;
					normal.y = - dx;
					normal.z = dy * 0.0;

					curNormal.copy( normal );

					normal.x += prevNormal.x;
					normal.y += prevNormal.y;
					normal.z += prevNormal.z;

					normal.normalize();

					initNormals.push( normal.x, normal.y, normal.z );

					prevNormal.copy( curNormal );

			}

		}

		// generate vertices, uvs and normals

		for ( let i = 0; i <= segments; i ++ ) {

			const phi = phiStart + i * inverseSegments * phiLength;

			const sin = Math.sin( phi );
			const cos = Math.cos( phi );

			for ( let j = 0; j <= ( points.length - 1 ); j ++ ) {

				// vertex

				vertex.x = points[ j ].x * sin;
				vertex.y = points[ j ].y;
				vertex.z = points[ j ].x * cos;

				vertices.push( vertex.x, vertex.y, vertex.z );

				// uv

				uv.x = i / segments;
				uv.y = j / ( points.length - 1 );

				uvs.push( uv.x, uv.y );

				// normal

				const x = initNormals[ 3 * j + 0 ] * sin;
				const y = initNormals[ 3 * j + 1 ];
				const z = initNormals[ 3 * j + 0 ] * cos;

				normals.push( x, y, z );

			}

		}

		// indices

		for ( let i = 0; i < segments; i ++ ) {

			for ( let j = 0; j < ( points.length - 1 ); j ++ ) {

				const base = j + i * points.length;

				const a = base;
				const b = base + points.length;
				const c = base + points.length + 1;
				const d = base + 1;

				// faces

				indices.push( a, b, d );
				indices.push( c, d, b );

			}

		}

		// build geometry

		this.setIndex( indices );
		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
		this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );

	}

	static fromJSON( data ) {

		return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength );

	}

}

export { LatheGeometry, LatheGeometry as LatheBufferGeometry };
<|endoftext|>


<commit_before>/**
 * Copyright 2012 Google, Inc. 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.
 */

/**
 * @author benvanik@google.com (Ben Vanik)
 */

goog.provide('gf.sim.commands');

goog.require('gf.sim.CommandFactory');
goog.require('gf.sim.commands.SetRootEntityCommand');
goog.require('gf.sim.commands.ReparentCommand');

/**
 * Registers all GF commands with a simulator.
 * @param {!gf.sim.Simulator} simulator Simulator.
 */
gf.sim.commands.registerCommands = function(simulator) {
  // REPARENT
  simulator.registerCommandFactory(new gf.sim.CommandFactory(
      gf.sim.commands.ReparentCommand.ID,
      gf.sim.commands.ReparentCommand,
      gf.sim.commands.ReparentCommand.FLAGS));

  // SET_ROOT_ENTITY
  simulator.registerCommandFactory(new gf.sim.CommandFactory(
      gf.sim.commands.SetRootEntityCommand.ID,
      gf.sim.commands.SetRootEntityCommand,
      gf.sim.commands.SetRootEntityCommand.FLAGS));
};
<commit_msg>Lint fix<commit_after>/**
 * Copyright 2012 Google, Inc. 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.
 */

/**
 * @author benvanik@google.com (Ben Vanik)
 */

goog.provide('gf.sim.commands');

goog.require('gf.sim.CommandFactory');
goog.require('gf.sim.commands.ReparentCommand');
goog.require('gf.sim.commands.SetRootEntityCommand');

/**
 * Registers all GF commands with a simulator.
 * @param {!gf.sim.Simulator} simulator Simulator.
 */
gf.sim.commands.registerCommands = function(simulator) {
  // REPARENT
  simulator.registerCommandFactory(new gf.sim.CommandFactory(
      gf.sim.commands.ReparentCommand.ID,
      gf.sim.commands.ReparentCommand,
      gf.sim.commands.ReparentCommand.FLAGS));

  // SET_ROOT_ENTITY
  simulator.registerCommandFactory(new gf.sim.CommandFactory(
      gf.sim.commands.SetRootEntityCommand.ID,
      gf.sim.commands.SetRootEntityCommand,
      gf.sim.commands.SetRootEntityCommand.FLAGS));
};
<|endoftext|>


<commit_before>/**
 * This is a prototype model representing a file in file browser.
 * New implementation with data-space support.
 * @module models/file
 * @author Łukasz Opioła
 * @author Jakub Liput
 * @copyright (C) 2016 ACK CYFRONET AGH
 * @license This software is released under the MIT license cited in 'LICENSE.txt'.
 */

import DS from 'ember-data';
import Ember from 'ember';
/* globals moment */

export default DS.Model.extend({
  errorNotifier: Ember.inject.service('errorNotifier'),
  notify: Ember.inject.service('notify'),

  name: DS.attr('string'),
  /**
    Specifies is this object a regular file ("file") or directory ("dir")
    To check if it is a dir please use "isDir" property.
  */
  type: DS.attr('string'),
  parent: DS.belongsTo('file', {inverse: 'children', async: true}),
  children: DS.hasMany('file', {inverse: 'parent', async: true}),

  modificationTime: DS.attr('number'),
  size: DS.attr('number'),
  permissions: DS.attr('number'),

  isExpanded: false,
  isSelected: false,

  // TODO: implement B, MB, GB, TODO: move to helper
  sizeHumanReadable: function() {
    return `${this.get('size')} B`;
  }.property('size'),

  modificationMoment: function() {
    let timestamp = this.get('modificationTime');
    return timestamp ? moment(timestamp * 1000).format('YYYY-MM-DD HH:MM') : '-';
  }.property('modificationTime'),

  permissionsHumanReadable: function() {
    let perms = this.get('permissions');
    return (1000 + perms).toString().substring(1);
  }.property('permissions'),

  isDir: function () {
    return this.get('type') === 'dir';
  }.property('type'),

  resetBrowserState() {
    this.set('isExpanded', false);
    this.set('isSelected', false);
  },

  resetBrowserStateRecursive() {
    this.get('children').forEach((child) => child.resetBrowserStateRecursive());
    this.resetBrowserState();
  },

  // TODO: doc, destroy, not destroyRecord!
  destroyRecursive() {
    // TODO: onsuccess onfailure...
    let children = this.get('children');
    let file = this;
    let deleteChildren = function() {
      file.get('notify').success('File removed');
      if (children) {
        children.forEach((child) => {
          child.deleteRecursive();
        });
      } else {
        console.debug('After destroy of ' + file.get('id') + ' there is no children');
      }
    };

    this.destroyRecord().then(deleteChildren, (failMessage) => {
      file.get('errorNotifier').handle(failMessage);
      file.rollbackAttributes();
    });
  },

  deleteRecursive() {
    console.debug('Will delete recursive: ' + this.get('id'));
    let children = this.get('children');
    console.debug('Children to delete: ' + children.map((c) => c.get('i')));
    if (children && children.get('length') > 0) {
      children.forEach((child) => {
        child.deleteRecursive();
      });
    }
    console.debug('Deleting file: ' + this.get('id'));
    // remove self from parent children list - issues here TODO!
    // TODO: perfomance issues on big trees?
    let parent = this.get('parent');
    let parentChildren = parent.get('children');
    if (parent && parentChildren) {
      parent.set('children',
        parentChildren.filter((child) => child.get('id') !== this.get('id'))
      );
      this.set('parent', null);
    }
    this.deleteRecord();
    console.debug('File: ' + this.get('id') + ' isDeleted: ' + this.get('isDeleted'));
  },

  isVisible: function () {
    var visible = this.get('parent.isExpanded');
    console.log('deselect(' + this.get('name') + '): ' +
      (this.get('isSelected') && !visible));
    if (this.get('isSelected') && !visible) {
      this.set('isSelected', false);
    }
    return visible;
  }.property('parent.isExpanded'),

  /// Utils

  /**
   * Returns array with file parents, including the file.
   * The array is ordered from root dir to given file (from parents to children).
   *
   * @param file - a leaf file of path to find
   * @returns {Array} array of Files
   */
  dirsPath() {
    let path = [this];
    let parent = this.get('parent');
    while (parent && parent.get('id')) {
      path.unshift(parent);
      parent = parent.get('parent');
    }
    console.debug(`Computed path for file ${this.get('id')}: ${JSON.stringify(path)}`);
    return path;
  },

  // TODO: may not update properly
  path: function() {
    return this.dirsPath().map(f => f.get('name')).join('/');
  }.property('parent'),

  // TODO: move directory utils to mixin
  /// Directory utils

  onlyDirectory() {
    if (!this.get('isDir')) {
      throw 'This file is not a directory!';
    }
  },

  hasSubDirs: function() {
    this.onlyDirectory();
    return this.get('children').filter((child) => child.get('isDir'))
      .length > 0;
  }.property('children.@each.isDir'),

  selectedFiles: function() {
    this.onlyDirectory();
    return this.get('children').filter((file) => file.get('isSelected'));
  }.property('children.@each.isSelected'),

  singleSelectedFile: function() {
    this.onlyDirectory();
    let selected = this.get('selectedFiles');
    return selected.length === 1 ? selected[0] : null;
  }.property('selectedFiles'),

  isSomeFileSelected: function() {
    this.onlyDirectory();
    return this.get('selectedFiles.length') > 0;
  }.property('selectedFiles'),

  removeSelectedFiles() {
    this.onlyDirectory();
    this.get('selectedFiles').forEach((file) => {
      file.destroyRecursive();
    });
  },

  setSelectedFilesPermissions(permissions) {
    this.get('selectedFiles').forEach((file) => {
      file.set('permissions', parseInt(permissions));
      // TODO: handle errors
      file.save();
    });
  },

  /** Creates file in this directory (only if this.isDir()) */
  createFile(type, fileName) {
    this.onlyDirectory();
    let record = this.get('store').createRecord('file', {
      name: fileName,
      parent: this,
      type: type
    });
    record.save().then(() => {}, (failMessage) => {
      this.get('errorNotifier').handle(failMessage);
      record.destroy();
    });
  },

});
<commit_msg>VFS-1549: File size display units<commit_after>/**
 * This is a prototype model representing a file in file browser.
 * New implementation with data-space support.
 * @module models/file
 * @author Łukasz Opioła
 * @author Jakub Liput
 * @copyright (C) 2016 ACK CYFRONET AGH
 * @license This software is released under the MIT license cited in 'LICENSE.txt'.
 */

import DS from 'ember-data';
import Ember from 'ember';
/* globals moment */

export default DS.Model.extend({
  errorNotifier: Ember.inject.service('errorNotifier'),
  notify: Ember.inject.service('notify'),

  name: DS.attr('string'),
  /**
    Specifies is this object a regular file ("file") or directory ("dir")
    To check if it is a dir please use "isDir" property.
  */
  type: DS.attr('string'),
  parent: DS.belongsTo('file', {inverse: 'children', async: true}),
  children: DS.hasMany('file', {inverse: 'parent', async: true}),

  modificationTime: DS.attr('number'),
  size: DS.attr('number'),
  permissions: DS.attr('number'),

  isExpanded: false,
  isSelected: false,

  // TODO: implement B, MB, GB, TODO: move to helper
  sizeHumanReadable: function() {
    let bytes = this.get('size');
    if (!bytes) {
      return '';
    }

    let number = bytes;
    let unit = 'B';
    if (bytes > 999999999) {
      unit = 'GB';
      number = bytes/1000000000;
    } else if (bytes > 999999) {
      unit = 'MB';
      number = bytes/1000000;
    } else if (bytes > 999) {
      unit = 'KB';
      number = bytes/1000;
    }
    return `${number.toFixed(2)} ${unit}`;
  }.property('size'),

  modificationMoment: function() {
    let timestamp = this.get('modificationTime');
    return timestamp ? moment(timestamp * 1000).format('YYYY-MM-DD HH:MM') : '-';
  }.property('modificationTime'),

  permissionsHumanReadable: function() {
    let perms = this.get('permissions');
    return (1000 + perms).toString().substring(1);
  }.property('permissions'),

  isDir: function () {
    return this.get('type') === 'dir';
  }.property('type'),

  resetBrowserState() {
    this.set('isExpanded', false);
    this.set('isSelected', false);
  },

  resetBrowserStateRecursive() {
    this.get('children').forEach((child) => child.resetBrowserStateRecursive());
    this.resetBrowserState();
  },

  // TODO: doc, destroy, not destroyRecord!
  destroyRecursive() {
    // TODO: onsuccess onfailure...
    let children = this.get('children');
    let file = this;
    let deleteChildren = function() {
      file.get('notify').success('File removed');
      if (children) {
        children.forEach((child) => {
          child.deleteRecursive();
        });
      } else {
        console.debug('After destroy of ' + file.get('id') + ' there is no children');
      }
    };

    this.destroyRecord().then(deleteChildren, (failMessage) => {
      file.get('errorNotifier').handle(failMessage);
      file.rollbackAttributes();
    });
  },

  deleteRecursive() {
    console.debug('Will delete recursive: ' + this.get('id'));
    let children = this.get('children');
    console.debug('Children to delete: ' + children.map((c) => c.get('i')));
    if (children && children.get('length') > 0) {
      children.forEach((child) => {
        child.deleteRecursive();
      });
    }
    console.debug('Deleting file: ' + this.get('id'));
    // remove self from parent children list - issues here TODO!
    // TODO: perfomance issues on big trees?
    let parent = this.get('parent');
    let parentChildren = parent.get('children');
    if (parent && parentChildren) {
      parent.set('children',
        parentChildren.filter((child) => child.get('id') !== this.get('id'))
      );
      this.set('parent', null);
    }
    this.deleteRecord();
    console.debug('File: ' + this.get('id') + ' isDeleted: ' + this.get('isDeleted'));
  },

  isVisible: function () {
    var visible = this.get('parent.isExpanded');
    console.log('deselect(' + this.get('name') + '): ' +
      (this.get('isSelected') && !visible));
    if (this.get('isSelected') && !visible) {
      this.set('isSelected', false);
    }
    return visible;
  }.property('parent.isExpanded'),

  /// Utils

  /**
   * Returns array with file parents, including the file.
   * The array is ordered from root dir to given file (from parents to children).
   *
   * @param file - a leaf file of path to find
   * @returns {Array} array of Files
   */
  dirsPath() {
    let path = [this];
    let parent = this.get('parent');
    while (parent && parent.get('id')) {
      path.unshift(parent);
      parent = parent.get('parent');
    }
    console.debug(`Computed path for file ${this.get('id')}: ${JSON.stringify(path)}`);
    return path;
  },

  // TODO: may not update properly
  path: function() {
    return this.dirsPath().map(f => f.get('name')).join('/');
  }.property('parent'),

  // TODO: move directory utils to mixin
  /// Directory utils

  onlyDirectory() {
    if (!this.get('isDir')) {
      throw 'This file is not a directory!';
    }
  },

  hasSubDirs: function() {
    this.onlyDirectory();
    return this.get('children').filter((child) => child.get('isDir'))
      .length > 0;
  }.property('children.@each.isDir'),

  selectedFiles: function() {
    this.onlyDirectory();
    return this.get('children').filter((file) => file.get('isSelected'));
  }.property('children.@each.isSelected'),

  singleSelectedFile: function() {
    this.onlyDirectory();
    let selected = this.get('selectedFiles');
    return selected.length === 1 ? selected[0] : null;
  }.property('selectedFiles'),

  isSomeFileSelected: function() {
    this.onlyDirectory();
    return this.get('selectedFiles.length') > 0;
  }.property('selectedFiles'),

  removeSelectedFiles() {
    this.onlyDirectory();
    this.get('selectedFiles').forEach((file) => {
      file.destroyRecursive();
    });
  },

  setSelectedFilesPermissions(permissions) {
    this.get('selectedFiles').forEach((file) => {
      file.set('permissions', parseInt(permissions));
      // TODO: handle errors
      file.save();
    });
  },

  /** Creates file in this directory (only if this.isDir()) */
  createFile(type, fileName) {
    this.onlyDirectory();
    let record = this.get('store').createRecord('file', {
      name: fileName,
      parent: this,
      type: type
    });
    record.save().then(() => {}, (failMessage) => {
      this.get('errorNotifier').handle(failMessage);
      record.destroy();
    });
  },

});
<|endoftext|>


<commit_before>/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module gap-inspector.js
*/

(function() {

  var _ = require('underscore');
  var FluentDate = require('../core/fluent-date');

  /**
  Identify gaps in the candidate's employment history.
  @class gapInspector
  */
  var gapInspector = module.exports = {

    moniker: 'gap-inspector',

    /**
    Run the Gap Analyzer on a resume.
    @method run
    @return An array of object representing gaps in the candidate's employment
    history. Each object provides the start, end, and duration of the gap:
        { <-- gap
          start: // A Moment.js date
          end: // A Moment.js date
          duration: // Gap length
        }
    */
    run: function( rez ) {

      // Convert the candidate's employment history to an array of dates,
      // where each element in the array is a start date or an end date of a
      // job -- it doesn't matter which.
      var new_e = rez.employment.history.map( function( job ){
        var obj = _.pairs( _.pick( job, ['start', 'end'] ) );
        obj[0][1] = FluentDate.fmt( obj[0][1] );
        if( obj.length > 1 )
          obj[1][1] = FluentDate.fmt( obj[1][1] );
        return obj;
      });

      // Flatten the array.
      new_e = _.flatten( new_e, true );

      // Sort the array, mixing start dates and end dates together
      new_e = _.sortBy( new_e, function( elem ) { return elem[1].unix(); });

      // Iterative over elements in the array. Each time a start date is found,
      // increment a reference count. Each time an end date is found, decrement
      // the reference count. When the reference count reaches 0, we have a gap.
      // When the reference count is > 0, the candidate is employed.
      var num_gaps = 0, ref_count = 0, gap_start, total_days = 0, total_work_days = 0,
        coverage = { gaps: [], overlaps: [] };
      new_e.forEach( function(point) {
        var inc = point[0] === 'start' ? 1 : -1;
        ref_count += inc;
        if( ref_count === 0 ) {
          coverage.gaps.push( { start: point[1], end: null });
        }
        else if( ref_count === 1 && inc === 1 ) {
          var lastGap = _.last( coverage.gaps );
          if( lastGap ) {
            lastGap.end = point[1];
            lastGap.duration = lastGap.end.diff( lastGap.start, 'days' );
            total_days += lastGap.duration;
          }
        }
        else if( ref_count === 2 && inc === 1 ) {
          coverage.overlaps.push( { start: point[1], end: null });
        }
        else if( ref_count === 1 && inc === -1 ) {
          var lastOverlap = _.last( coverage.overlaps );
          if( lastOverlap ) {
            lastOverlap.end = point[1];
            lastOverlap.duration = lastOverlap.end.diff( lastOverlap.start, 'days' );
            if( lastOverlap.duration === 0 ) {
              coverage.overlaps.pop();
            }
            total_work_days += lastOverlap.duration;
          }
        }
      });

      // var now = moment();
      // _.each( coverage.overlaps, function(ol) {
      //   return ol.end = ol.end || now;
      // });
      if( !_.last( coverage.overlaps ).end ) {
        var l = _.last( coverage.overlaps );
        l.end = moment();
        l.duration = l.end.diff( l.start, 'days' );
      }

      coverage.duration = total_days;
      coverage.pct = ( total_days > 0 ) ?
        (100.0 - ( total_days / rez.duration('days') * 100)).toFixed(1) + '%' :
        '???';

      return coverage;
    }

  };

}());
<commit_msg>Handle missing dates during gap inspection.<commit_after>/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module gap-inspector.js
*/

(function() {

  var _ = require('underscore');
  var FluentDate = require('../core/fluent-date');
  var moment = require('moment');

  /**
  Identify gaps in the candidate's employment history.
  @class gapInspector
  */
  var gapInspector = module.exports = {

    moniker: 'gap-inspector',

    /**
    Run the Gap Analyzer on a resume.
    @method run
    @return An array of object representing gaps in the candidate's employment
    history. Each object provides the start, end, and duration of the gap:
        { <-- gap
          start: // A Moment.js date
          end: // A Moment.js date
          duration: // Gap length
        }
    */
    run: function( rez ) {

      // Convert the candidate's employment history to an array of dates,
      // where each element in the array is a start date or an end date of a
      // job -- it doesn't matter which.
      var new_e = rez.employment.history.map( function( job ){
        var obj = _.pick( job, ['start', 'end'] );
        if( obj && (obj.start || obj.end)) {
          obj = _.pairs( obj );
          obj[0][1] = FluentDate.fmt( obj[0][1] );
          if( obj.length > 1 )
            obj[1][1] = FluentDate.fmt( obj[1][1] );
        }
        return obj;
      });

      // Flatten the array.
      new_e = _.flatten( new_e, true );

      // Remove empties (objects that had no .start or .end date)
      new_e = _.omit( new_e, function(v) {
        var isEmpty = ( !v || !v.length || !v[0] || !v[0].length );
        if( isEmpty ) console.log('Found empty');
        return isEmpty;
      });

      // Sort the array, mixing start dates and end dates together
      new_e = _.sortBy( new_e, function( elem ) { return elem[1].unix(); });

      var num_gaps = 0
        , ref_count = 0
        , total_days = 0
        , total_work_days = 0
        , coverage = { gaps: [], overlaps: [] }
        , gap_start;

      // Iterative over elements in the array. Each time a start date is found,
      // increment a reference count. Each time an end date is found, decrement
      // the reference count. When the reference count reaches 0, we have a gap.
      // When the reference count is > 0, the candidate is employed.

      new_e.forEach( function(point) {
        var inc = point[0] === 'start' ? 1 : -1;
        ref_count += inc;
        if( ref_count === 0 ) {
          coverage.gaps.push( { start: point[1], end: null });
        }
        else if( ref_count === 1 && inc === 1 ) {
          var lastGap = _.last( coverage.gaps );
          if( lastGap ) {
            lastGap.end = point[1];
            lastGap.duration = lastGap.end.diff( lastGap.start, 'days' );
            total_days += lastGap.duration;
          }
        }
        else if( ref_count === 2 && inc === 1 ) {
          coverage.overlaps.push( { start: point[1], end: null });
        }
        else if( ref_count === 1 && inc === -1 ) {
          var lastOverlap = _.last( coverage.overlaps );
          if( lastOverlap ) {
            lastOverlap.end = point[1];
            lastOverlap.duration = lastOverlap.end.diff( lastOverlap.start, 'days' );
            if( lastOverlap.duration === 0 ) {
              coverage.overlaps.pop();
            }
            total_work_days += lastOverlap.duration;
          }
        }
      });

      // var now = moment();
      // _.each( coverage.overlaps, function(ol) {
      //   return ol.end = ol.end || now;
      // });
      if( coverage.overlaps.length ) {
        if( !_.last( coverage.overlaps ).end ) {
          var l = _.last( coverage.overlaps );
          l.end = moment();
          l.duration = l.end.diff( l.start, 'days' );
        }
      }

      coverage.duration = total_days;
      coverage.pct = ( total_days > 0 ) ?
        (100.0 - ( total_days / rez.duration('days') * 100)).toFixed(1) + '%' :
        '???';

      return coverage;
    }

  };

}());
<|endoftext|>