<commit_before>/**
 *  Jelly. a sweet unobtrusive javascript framework
 *  for jQuery and Rails
 *
 *  version 0.8.1
 *
 * Copyright (c) 2009 Pivotal Labs
 * Licensed under the MIT license.
 *
 *  * Date: 2009-07-20 9:50:50 (Mon, 20 Jul 2009)
 *
 */

if (!window.Jelly) Jelly = new Object();
if (!Function.prototype.bind) {
  Function.prototype.bind = function(object) {
    var self = this;
    return function() {
      return self.apply(object, arguments);
    }
  }
}
Jelly.init = function() {
  this.observers = [];
  this.observers.pending = [];
  this.attach = this.Observers.attach;
  this.notifyObservers = this.Observers.notify;
  this.Components.initCalled = false;
  this.Pages.init();
  var self = this;
  $(document).ready(function() {
    self.Components.init();
  });
};

Jelly.Components = {
  init: function() {
    for (var i = 0; i < Jelly.observers.pending.length; i++) {
      Jelly.Observers.initPending.call(Jelly.observers, Jelly.observers.pending[i]);
    }
    this.initCalled = true;
  }
};

Jelly.Observers = {
  attach: function() {
    if (this == Jelly) {
      return Jelly.Observers.attach.apply(this.observers, arguments);
    }
    this.pending = this.pending || [];
    for (var i = 0; i < arguments.length; i++) {
      var definition = arguments[i];
      var component = (typeof definition.component == "string") ?
                      eval(definition.component) :
                      definition.component;
      var evaluatedDefinition = {
        component: component,
        arguments: definition.arguments
      };
      this.pending.push(evaluatedDefinition);
      if (Jelly.Components.initCalled) {
        Jelly.Observers.initPending.call(this, evaluatedDefinition);
      }
    }
  },

  initPending: function(definition) {
    var observer;
    if (definition.component.init) {
      observer = definition.component.init.apply(definition.component, definition.arguments);
    }
    this.push(observer ? observer : definition.component);
  },

  notify: function(callbacks) {
    if (this == Jelly) {
      return Jelly.Observers.notify.apply(this.observers, arguments);
    }
    if (!$.isArray(callbacks)) {
      callbacks = [callbacks];
    }

    var observers = this.slice(0);
    for (var i = 0; i < callbacks.length; i++) {
      var callback = callbacks[i];

      // Deprecate 'on' in favor of making each page action a Component.
      if (callback.on) {
        var additionalObserver = eval(callback.on);
        if (observers.indexOf(additionalObserver) == -1) {
          observers.push(additionalObserver);
        }
      }

      for (var j = 0; j < observers.length; j++) {
        var observer = observers[j];
        if (observer[callback.method]) {
          if (observer.detach && observer.detach()) {
            Jelly.Observers.garbageCollectObserver.call(this, observer);
          } else {
            observer[callback.method].apply(observer, callback.arguments);
          }
        }
      }

      if (callback.attach) {
        Jelly.Observers.attach.apply(this, callback.attach);
      }
    }
  },

  garbageCollectObserver: function(observer) {
    var index = this.indexOf(observer);
    if (index > -1) {
      Jelly.Observers.remove.call(this, index, index + 1);
    }
  },

  remove: function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  }
};

Jelly.Pages = {
  init: function() {
    this.all = {};
    Jelly.all = this.all; // Deprecated
  },

  add: function(name) {
    var page = new Jelly.Page.Constructor(name);
    for (var i = 1; i < arguments.length; i++) {
      $.extend(page, arguments[i]);
    }
    return page;
  }
};
Jelly.add = Jelly.Pages.add; // Deprecated

Jelly.Page = {
  init: function(controllerName, actionName) {
    var page = Jelly.Pages.all[controllerName] || new Jelly.Page.Constructor(controllerName);
    window.page = page;
    if (page.all) page.all();
    if (page[actionName]) page[actionName].call(page);
    page.loaded = true;
    return page;
  },
  Constructor: function(name) {
    this.loaded = false;
    this.documentHref = Jelly.Location.documentHref;

    this.name = name;
    Jelly.Pages.all[name] = this;
  }
};

Jelly.Location = {
  init: function() {
  },

  on_redirect: function(location) {
    top.location.href = location;
  }
};

Jelly.init();
<commit_msg>Bump.<commit_after>/**
 *  Jelly. a sweet unobtrusive javascript framework
 *  for jQuery and Rails
 *
 *  version 0.8.2
 *
 * Copyright (c) 2009 Pivotal Labs
 * Licensed under the MIT license.
 *
 *  * Date: 2009-07-20 9:50:50 (Mon, 20 Jul 2009)
 *
 */

if (!window.Jelly) Jelly = new Object();
if (!Function.prototype.bind) {
  Function.prototype.bind = function(object) {
    var self = this;
    return function() {
      return self.apply(object, arguments);
    }
  }
}
Jelly.init = function() {
  this.observers = [];
  this.observers.pending = [];
  this.attach = this.Observers.attach;
  this.notifyObservers = this.Observers.notify;
  this.Components.initCalled = false;
  this.Pages.init();
  var self = this;
  $(document).ready(function() {
    self.Components.init();
  });
};

Jelly.Components = {
  init: function() {
    for (var i = 0; i < Jelly.observers.pending.length; i++) {
      Jelly.Observers.initPending.call(Jelly.observers, Jelly.observers.pending[i]);
    }
    this.initCalled = true;
  }
};

Jelly.Observers = {
  attach: function() {
    if (this == Jelly) {
      return Jelly.Observers.attach.apply(this.observers, arguments);
    }
    this.pending = this.pending || [];
    for (var i = 0; i < arguments.length; i++) {
      var definition = arguments[i];
      var component = (typeof definition.component == "string") ?
                      eval(definition.component) :
                      definition.component;
      var evaluatedDefinition = {
        component: component,
        arguments: definition.arguments
      };
      this.pending.push(evaluatedDefinition);
      if (Jelly.Components.initCalled) {
        Jelly.Observers.initPending.call(this, evaluatedDefinition);
      }
    }
  },

  initPending: function(definition) {
    var observer;
    if (definition.component.init) {
      observer = definition.component.init.apply(definition.component, definition.arguments);
    }
    this.push(observer ? observer : definition.component);
  },

  notify: function(callbacks) {
    if (this == Jelly) {
      return Jelly.Observers.notify.apply(this.observers, arguments);
    }
    if (!$.isArray(callbacks)) {
      callbacks = [callbacks];
    }

    var observers = this.slice(0);
    for (var i = 0; i < callbacks.length; i++) {
      var callback = callbacks[i];

      // Deprecate 'on' in favor of making each page action a Component.
      if (callback.on) {
        var additionalObserver = eval(callback.on);
        if (observers.indexOf(additionalObserver) == -1) {
          observers.push(additionalObserver);
        }
      }

      for (var j = 0; j < observers.length; j++) {
        var observer = observers[j];
        if (observer[callback.method]) {
          if (observer.detach && observer.detach()) {
            Jelly.Observers.garbageCollectObserver.call(this, observer);
          } else {
            observer[callback.method].apply(observer, callback.arguments);
          }
        }
      }

      if (callback.attach) {
        Jelly.Observers.attach.apply(this, callback.attach);
      }
    }
  },

  garbageCollectObserver: function(observer) {
    var index = this.indexOf(observer);
    if (index > -1) {
      Jelly.Observers.remove.call(this, index, index + 1);
    }
  },

  remove: function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  }
};

Jelly.Pages = {
  init: function() {
    this.all = {};
    Jelly.all = this.all; // Deprecated
  },

  add: function(name) {
    var page = new Jelly.Page.Constructor(name);
    for (var i = 1; i < arguments.length; i++) {
      $.extend(page, arguments[i]);
    }
    return page;
  }
};
Jelly.add = Jelly.Pages.add; // Deprecated

Jelly.Page = {
  init: function(controllerName, actionName) {
    var page = Jelly.Pages.all[controllerName] || new Jelly.Page.Constructor(controllerName);
    window.page = page;
    if (page.all) page.all();
    if (page[actionName]) page[actionName].call(page);
    page.loaded = true;
    return page;
  },
  Constructor: function(name) {
    this.loaded = false;
    this.documentHref = Jelly.Location.documentHref;

    this.name = name;
    Jelly.Pages.all[name] = this;
  }
};

Jelly.Location = {
  init: function() {
  },

  on_redirect: function(location) {
    top.location.href = location;
  }
};

Jelly.init();
<|endoftext|>


<commit_before>import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import { registerListener, unregisterAllListeners, fireEvent } from 'c/pubsubNoPageRef';
import createDataImportFieldMapping
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.createDataImportFieldMapping';

export default class bdiFieldMappingModal extends LightningElement {

    @api objectMapping;
    @api fieldMappingSetName;
    @api isModalOpen;
    @api diFieldDescribes;
    @api targetObjectFieldDescribes;

    @track isLoading;
    @track row;

    @track selectedSourceFieldLabel;
    @track selectedSourceFieldAPIName;
    @track sourceFieldLabelOptions;
    @track selectedSourceFieldDisplayType;
    @track hasSourceFieldErrors;

    @track selectedTargetFieldLabel;
    @track selectedTargetFieldAPIName;
    @track targetFieldLabelOptions;
    @track selectedTargetFieldDisplayType;
    @track hasTargetFieldErrors;

    @api diFieldsByAPIName;
    @api targetFieldsByAPIName;

    @api targetFieldsByLabelByDisplayType;

    @track mappedDIFieldLabels = [];
    @track mappedTargetFieldLabels = [];

    // Map of Display Types
    validTargetTypesBySourceType = {
        "Id": ["Id", "String"],
        "Reference": ["Reference", "String"],
        "Phone": ["Phone", "String"],
        "Textarea": ["Textarea", "String"],
        "Url": ["Url", "String"],
        "Email": ["Email", "String"],
        "Boolean": ["Boolean"],
        "String": ["String", "Picklist"],
        "Datetime": ["Datetime"],
        "Date": ["Date"],
        "Picklist": ["Picklist"], // Todo: Include Sometimes Boolean as per the Bdi Mapping Field Types Dc
        "Currency": ["Currency"],
        "Percent": ["Percent"],
        "Integer": ["Integer"]
    };

    get isTargetFieldDisabled() {
        if ((this.selectedSourceFieldAPIName || this.selectedSourceFieldLabel) &&
            (this.targetFieldLabelOptions && this.targetFieldLabelOptions.length > 0)) {
            return false;
        }
        return true;
    }

    constructor() {
        super();
        this.escapeFunction = this.escapeFunction.bind(this);
    }

    connectedCallback() {
        document.addEventListener("keydown", this.escapeFunction, false);
        registerListener('openModal', this.handleOpenModal, this);
        registerListener('closeModal', this.handleCloseModal, this);

        // Register listeners for child searchable combobox components
        registerListener('sourceFieldLabelChange', this.handleSourceFieldLabelChange, this);
        registerListener('targetFieldLabelChange', this.handleTargetFieldLabelChange, this);
    }

    disconnectedCallback() {
        document.removeEventListener("keydown", this.escapeFunction, false);
        unregisterAllListeners(this);
    }

    /*******************************************************************************
    * @description Handles the open modal event from bdiFieldMappings
    *
    * @param event: Event containing row details or lack of row details
    */
    handleOpenModal(event) {
        this.isModalOpen = true;
        this.isLoading = true;
        this.hasSourceFieldErrors = false;
        this.hasTargetFieldErrors = false;
        this.objectMapping = event.objectMapping;

        this.collectMappedDataImportFields(event.fieldMappings);

        this.row = event.row;

        if (this.row) {
            // Edit row
            this.selectedSourceFieldLabel = this.row.xxx_Source_Field_Label_xxx;
            this.selectedSourceFieldAPIName = this.row.xxx_Source_Field_API_Name_xxx;
            this.selectedTargetFieldAPIName = this.row.xxx_Target_Field_API_Name_xxx;
            this.selectedTargetFieldLabel = this.row.xxx_Target_Field_Label_xxx;
            this.selectedSourceFieldDisplayType = this.toTitleCase(this.row.xxx_Source_Field_Data_Type_xxx);
        } else {
            // New row
            this.clearSelections();
        }

        this.setDataImportFieldDescribes(this.diFieldDescribes);
        this.setTargetObjectFieldDescribes(this.targetObjectFieldDescribes);

        this.isLoading = false
    }

    /*******************************************************************************
    * @description Loops through and collects the source field label and target field
    * labels of existing field mappings for the currently selected object mapping
    *
    * @param {array} fieldMappings: List of field mappings from bdiFieldMappings
    */
    collectMappedDataImportFields(fieldMappings) {
        for (let i = 0; i < fieldMappings.length; i++) {
            let fieldMapping = fieldMappings[i];

            this.mappedDIFieldLabels.push(fieldMapping.xxx_Source_Field_Label_xxx);
            this.mappedTargetFieldLabels.push(fieldMapping.xxx_Target_Field_Label_xxx);
        }
    }

    /*******************************************************************************
    * @description Creates a map of data import field labels by field API name and a
    * map of data import field API names by field label.
    *
    * @param {FieldInfo[]} fieldInfos: List of BDI_ManageAdvancedMappingCtrl.FieldInfos
    * from the Data Import object
    */
    setDataImportFieldDescribes(fieldInfos) {
        this.sourceFieldLabelOptions = [];
        let diFieldsByAPIName = {};

        for (let i = 0; i < fieldInfos.length; i++) {

            // Include the data import field if it hasn't already been mapped
            // or if it's the currently selected field (i.e. editing)
            if (!this.mappedDIFieldLabels.includes(fieldInfos[i].label) ||
                this.selectedSourceFieldLabel === fieldInfos[i].label) {
                let labelOption = {
                    label: `${fieldInfos[i].label} (${fieldInfos[i].value})`,
                    value: fieldInfos[i].value
                }

                this.sourceFieldLabelOptions.push(labelOption);
                diFieldsByAPIName[labelOption.value] = this.parse(fieldInfos[i]);
            }
        }
        this.diFieldsByAPIName = diFieldsByAPIName;
    }

    /*******************************************************************************
    * @description Creates a map of target object field labels by field API name and a
    * map of target object field API names by field label.
    *
    * @param {FieldInfo[]} fieldInfos: List of BDI_ManageAdvancedMappingCtrl.FieldInfos
    * from the Data Import object
    */
    setTargetObjectFieldDescribes(fieldInfos) {
        this.targetFieldLabelOptions = [];
        let targetFieldsByAPIName = {};
        let targetFieldsByLabelByDisplayType = {};

        for (let i = 0; i < fieldInfos.length; i++) {

            // Include the data import field if it hasn't already been mapped
            // or if it's the currently selected field (i.e. editing)
            if (!this.mappedTargetFieldLabels.includes(fieldInfos[i].label) ||
                this.selectedTargetFieldLabel === fieldInfos[i].label) {
                let labelOption = {
                    label: `${fieldInfos[i].label} (${fieldInfos[i].value})`,
                    value: fieldInfos[i].value
                }

                this.targetFieldLabelOptions.push(labelOption);
                targetFieldsByAPIName[labelOption.value] = this.parse(fieldInfos[i]);

                let displayType = this.toTitleCase(fieldInfos[i].displayType);
                // Collect target fields by display type
                if (targetFieldsByLabelByDisplayType[displayType]) {
                    targetFieldsByLabelByDisplayType[displayType].push(labelOption);
                } else {
                    targetFieldsByLabelByDisplayType[displayType] = [labelOption];
                }
            }
        }

        this.targetFieldsByAPIName = targetFieldsByAPIName;
        this.targetFieldsByLabelByDisplayType = targetFieldsByLabelByDisplayType;

        if (this.selectedSourceFieldDisplayType) {
            this.handleAvailableTargetFieldsBySourceFieldDisplayType(this.selectedSourceFieldDisplayType);
        }
    }

    /*******************************************************************************
    * @description Clears out all the various "selected" properties
    */
    clearSelections() {
        this.selectedSourceFieldLabel = undefined;
        this.selectedSourceFieldAPIName = undefined;
        this.selectedTargetFieldLabel = undefined;
        this.selectedTargetFieldAPIName = undefined;
    }

    /*******************************************************************************
    * @description Handles the close modal event from bdiFieldMappings
    */
    handleCloseModal() {
        this.isModalOpen = false;
    }

    /*******************************************************************************
    * @description Handles escape key press and closes the modal
    */
    escapeFunction(event) {
        if (event.keyCode === 27) {
            this.handleCloseModal();
        }
    }

    /*******************************************************************************
    * @description Handles the creation or update of a row based on the existence of
    * the row property. If the row property exists, we only set the source and target
    * field API name, otherwise we set all the fields. Calls the handleDeploymentId
    * function on receiving an id back from createDataImportFieldMapping.
    */
    handleSave() {
        let rowDetails;

        if (this.row) {
            // Set source and target fields
            this.row.xxx_Source_Field_API_Name_xxx = this.selectedSourceFieldAPIName;
            this.row.xxx_Target_Field_API_Name_xxx = this.selectedTargetFieldAPIName;
            rowDetails = JSON.stringify(this.row);
        } else {
            // New Field Mapping
            rowDetails = JSON.stringify({
                DeveloperName: null,
                Label: this.selectedSourceFieldLabel,
                MasterLabel: this.selectedSourceFieldLabel,
                xxx_Data_Import_Field_Mapping_Set_xxx: this.fieldMappingSetName,
                xxx_Is_Deleted_xxx: false,
                xxx_Required_xxx: 'No',
                xxx_Source_Field_API_Name_xxx: this.selectedSourceFieldAPIName,
                xxx_Target_Field_API_Name_xxx: this.selectedTargetFieldAPIName,
                xxx_Target_Object_Mapping_xxx: this.objectMapping.DeveloperName
            });
        }

        let missingField = this.handleFieldValidations();

        if (missingField.length === 0) {
            this.isLoading = true;
            createDataImportFieldMapping({fieldMappingString: rowDetails})
                .then((deploymentId) => {
                    this.handleDeploymentId(deploymentId);
                })
                .catch((error) => {
                    this.isLoading = false;
                    if (error && error.body) {
                        this.showToast(
                            'Error',
                            '{0}. {1}. {2}.',
                            'error',
                            'sticky',
                            [error.body.exceptionType, error.body.message, error.body.stackTrace]);
                    }
                });
        } else {
            this.showToast(
                'Error',
                `Missing the following fields [${missingField}]`,
                'error',
                'dismissable');
        }
    }

    /*******************************************************************************
    * @description Adds error classes to comboboxes and returns a string indicating
    * which combobox selection is incomplete.
    *
    * @return {string} missingFields: Name of the incomplete combobox
    */
    handleFieldValidations() {
        let missingField = '';
        if (!this.selectedSourceFieldAPIName) {
            missingField = 'Source Field';
            this.hasSourceFieldErrors = true;
        }
        if (!this.selectedTargetFieldAPIName && !this.isTargetFieldDisabled) {
            missingField = 'Target Field';
            this.hasTargetFieldErrors = true;
        }

        return missingField;
    }

    /*******************************************************************************
    * @description Creates and dispatches a CustomEvent 'deployment' letting the
    * platformEventListener know that we have an id to register and monitor. After
    * dispatching the CustomEvent, start the deployment timeout on bdiFieldMappings.
    *
    * @param {string} deploymentId: Custom Metadata Deployment Id
    */
    handleDeploymentId(deploymentId) {
        const deploymentEvent = new CustomEvent('deployment', {
            bubbles: true,
            composed: true,
            detail: {deploymentId}
        });
        this.dispatchEvent(deploymentEvent);

        fireEvent(this.pageRef, 'startDeploymentTimeout', { deploymentId: deploymentId });
    }

    /*******************************************************************************
    * @description Handles the onchange event for the bdiFieldMappingModalComboboxSearch
    * sourceFieldLabel, sets the value for both sourceFieldLabel and sourceFieldAPIName,
    * and updates the available target fields based on the selected source field's display
    * type.
    *
    * @param {object} event: Event containing combobox selection details
    */
    handleSourceFieldLabelChange(event) {
        let fieldAPIName = event.detail.value;
        let fieldInfo = this.diFieldsByAPIName[fieldAPIName];
        this.selectedSourceFieldLabel = fieldInfo.label;
        this.selectedSourceFieldAPIName = fieldAPIName;
        this.selectedSourceFieldDisplayType = this.toTitleCase(fieldInfo.displayType);
        this.selectedTargetFieldAPIName = undefined;

        this.handleAvailableTargetFieldsBySourceFieldDisplayType(this.selectedSourceFieldDisplayType);
    }

    /*******************************************************************************
    * @description Filters the available target fields based on display type
    *
    * @param {string} displayType: Display Type of the currently selected source field
    */
    handleAvailableTargetFieldsBySourceFieldDisplayType(displayType) {
        this.targetFieldLabelOptions = [];
        let validTargetTypes = this.validTargetTypesBySourceType[displayType];

        for (let i = 0; i < validTargetTypes.length; i++) {
            let validType = validTargetTypes[i];
            let validTargetTypesByLabel = this.targetFieldsByLabelByDisplayType[validType] || [];

            this.targetFieldLabelOptions.push(...validTargetTypesByLabel);
        }
    }

    /*******************************************************************************
    * @description Handles the onchange event for the bdiFieldMappingModalComboboxSearch
    * targetFieldLabel, sets the value for both targetFieldLabel and targetFieldAPIName.
    *
    * @param {object} event: Event containing combobox selection details
    */
    handleTargetFieldLabelChange(event) {
        this.selectedTargetFieldAPIName = event.detail.value;
        let fieldInfo = this.targetFieldsByAPIName[this.selectedTargetFieldAPIName];
        this.selectedTargetFieldLabel = fieldInfo.label;
        this.selectedTargetFieldDisplayType = this.toTitleCase(fieldInfo.displayType);
    }

    /*******************************************************************************
    * @description Creates and dispatches a ShowToastEvent
    *
    * @param {string} title: Title of the toast, dispalyed as a heading.
    * @param {string} message: Message of the toast. It can contain placeholders in
    * the form of {0} ... {N}. The placeholders are replaced with the links from
    * messageData param
    * @param {string} mode: Mode of the toast
    * @param {array} messageData: List of values that replace the {index} placeholders
    * in the message param
    */
    showToast(title, message, variant, mode, messageData) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode,
            messageData: messageData
        });
        this.dispatchEvent(event);
    }

    /*******************************************************************************
    * @description Parse proxy objects for debugging, mutating, etc
    *
    * @param {object} obj: Object to be parsed
    */
    parse(obj) {
       return JSON.parse(JSON.stringify(obj));
    }

    /*******************************************************************************
    * @description Title cases a string
    *
    * @param {string} string: String to be title cased
    */
    toTitleCase(string) {
        string = string.toLowerCase().split(' ');
        for (let i = 0; i < string.length; i++) {
            string[i] = string[i].charAt(0).toUpperCase() + string[i].slice(1);
        }
        return string.join(' ');
    }
}<commit_msg>fix data types not displaying for target fields on first edit in modal<commit_after>import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import { registerListener, unregisterAllListeners, fireEvent } from 'c/pubsubNoPageRef';
import createDataImportFieldMapping
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.createDataImportFieldMapping';

export default class bdiFieldMappingModal extends LightningElement {

    @api objectMapping;
    @api fieldMappingSetName;
    @api isModalOpen;
    @api diFieldDescribes;
    @api targetObjectFieldDescribes;

    @track isLoading;
    @track row;

    @track selectedSourceFieldLabel;
    @track selectedSourceFieldAPIName;
    @track sourceFieldLabelOptions;
    @track selectedSourceFieldDisplayType;
    @track hasSourceFieldErrors;

    @track selectedTargetFieldLabel;
    @track selectedTargetFieldAPIName;
    @track targetFieldLabelOptions;
    @track selectedTargetFieldDisplayType;
    @track hasTargetFieldErrors;

    @api diFieldsByAPIName;
    @api targetFieldsByAPIName;

    @api targetFieldsByLabelByDisplayType;

    @track mappedDIFieldLabels = [];
    @track mappedTargetFieldLabels = [];

    // Map of Display Types
    validTargetTypesBySourceType = {
        "Id": ["Id", "String"],
        "Reference": ["Reference", "String"],
        "Phone": ["Phone", "String"],
        "Textarea": ["Textarea", "String"],
        "Url": ["Url", "String"],
        "Email": ["Email", "String"],
        "Boolean": ["Boolean"],
        "String": ["String", "Picklist"],
        "Datetime": ["Datetime"],
        "Date": ["Date"],
        "Picklist": ["Picklist"], // Todo: Include Sometimes Boolean as per the Bdi Mapping Field Types Dc
        "Currency": ["Currency"],
        "Percent": ["Percent"],
        "Integer": ["Integer"]
    };

    get isTargetFieldDisabled() {
        if ((this.selectedSourceFieldAPIName || this.selectedSourceFieldLabel) &&
            (this.targetFieldLabelOptions && this.targetFieldLabelOptions.length > 0)) {
            return false;
        }
        return true;
    }

    constructor() {
        super();
        this.escapeFunction = this.escapeFunction.bind(this);
    }

    connectedCallback() {
        document.addEventListener("keydown", this.escapeFunction, false);
        registerListener('openModal', this.handleOpenModal, this);
        registerListener('closeModal', this.handleCloseModal, this);

        // Register listeners for child searchable combobox components
        registerListener('sourceFieldLabelChange', this.handleSourceFieldLabelChange, this);
        registerListener('targetFieldLabelChange', this.handleTargetFieldLabelChange, this);
    }

    disconnectedCallback() {
        document.removeEventListener("keydown", this.escapeFunction, false);
        unregisterAllListeners(this);
    }

    /*******************************************************************************
    * @description Handles the open modal event from bdiFieldMappings
    *
    * @param event: Event containing row details or lack of row details
    */
    handleOpenModal(event) {
        try {
            this.isModalOpen = true;
            this.isLoading = true;
            this.hasSourceFieldErrors = false;
            this.hasTargetFieldErrors = false;
            this.objectMapping = event.objectMapping;

            this.collectMappedDataImportFields(event.fieldMappings);

            this.row = event.row;

            if (this.row) {
                // Edit row
                this.selectedSourceFieldLabel = this.row.xxx_Source_Field_Label_xxx;
                this.selectedSourceFieldAPIName = this.row.xxx_Source_Field_API_Name_xxx;
                this.selectedTargetFieldAPIName = this.row.xxx_Target_Field_API_Name_xxx;
                this.selectedTargetFieldLabel = this.row.xxx_Target_Field_Label_xxx;
                this.selectedSourceFieldDisplayType = this.toTitleCase(this.row.xxx_Source_Field_Data_Type_xxx);
                this.selectedTargetFieldDisplayType = this.toTitleCase(this.row.xxx_Target_Field_Data_Type_xxx);
            } else {
                // New row
                this.clearSelections();
            }

            this.setDataImportFieldDescribes(this.diFieldDescribes);
            this.setTargetObjectFieldDescribes(this.targetObjectFieldDescribes);
        } catch(error) {
            console.log('ERROR: ', error);
        }

        this.isLoading = false
    }

    /*******************************************************************************
    * @description Loops through and collects the source field label and target field
    * labels of existing field mappings for the currently selected object mapping
    *
    * @param {array} fieldMappings: List of field mappings from bdiFieldMappings
    */
    collectMappedDataImportFields(fieldMappings) {
        for (let i = 0; i < fieldMappings.length; i++) {
            let fieldMapping = fieldMappings[i];

            this.mappedDIFieldLabels.push(fieldMapping.xxx_Source_Field_Label_xxx);
            this.mappedTargetFieldLabels.push(fieldMapping.xxx_Target_Field_Label_xxx);
        }
    }

    /*******************************************************************************
    * @description Creates a map of data import field labels by field API name and a
    * map of data import field API names by field label.
    *
    * @param {FieldInfo[]} fieldInfos: List of BDI_ManageAdvancedMappingCtrl.FieldInfos
    * from the Data Import object
    */
    setDataImportFieldDescribes(fieldInfos) {
        this.sourceFieldLabelOptions = [];
        let diFieldsByAPIName = {};

        for (let i = 0; i < fieldInfos.length; i++) {

            // Include the data import field if it hasn't already been mapped
            // or if it's the currently selected field (i.e. editing)
            if (!this.mappedDIFieldLabels.includes(fieldInfos[i].label) ||
                this.selectedSourceFieldLabel === fieldInfos[i].label) {
                let labelOption = {
                    label: `${fieldInfos[i].label} (${fieldInfos[i].value})`,
                    value: fieldInfos[i].value
                }

                this.sourceFieldLabelOptions.push(labelOption);
                diFieldsByAPIName[labelOption.value] = this.parse(fieldInfos[i]);
            }
        }
        this.diFieldsByAPIName = diFieldsByAPIName;
    }

    /*******************************************************************************
    * @description Creates a map of target object field labels by field API name and a
    * map of target object field API names by field label.
    *
    * @param {FieldInfo[]} fieldInfos: List of BDI_ManageAdvancedMappingCtrl.FieldInfos
    * from the Data Import object
    */
    setTargetObjectFieldDescribes(fieldInfos) {
        this.targetFieldLabelOptions = [];
        let targetFieldsByAPIName = {};
        let targetFieldsByLabelByDisplayType = {};

        for (let i = 0; i < fieldInfos.length; i++) {

            // Include the data import field if it hasn't already been mapped
            // or if it's the currently selected field (i.e. editing)
            if (!this.mappedTargetFieldLabels.includes(fieldInfos[i].label) ||
                this.selectedTargetFieldLabel === fieldInfos[i].label) {
                let labelOption = {
                    label: `${fieldInfos[i].label} (${fieldInfos[i].value})`,
                    value: fieldInfos[i].value
                }

                this.targetFieldLabelOptions.push(labelOption);
                targetFieldsByAPIName[labelOption.value] = this.parse(fieldInfos[i]);

                let displayType = this.toTitleCase(fieldInfos[i].displayType);
                // Collect target fields by display type
                if (targetFieldsByLabelByDisplayType[displayType]) {
                    targetFieldsByLabelByDisplayType[displayType].push(labelOption);
                } else {
                    targetFieldsByLabelByDisplayType[displayType] = [labelOption];
                }
            }
        }

        this.targetFieldsByAPIName = targetFieldsByAPIName;
        this.targetFieldsByLabelByDisplayType = targetFieldsByLabelByDisplayType;

        if (this.selectedSourceFieldDisplayType) {
            this.handleAvailableTargetFieldsBySourceFieldDisplayType(this.selectedSourceFieldDisplayType);
        }
    }

    /*******************************************************************************
    * @description Clears out all the various "selected" properties
    */
    clearSelections() {
        this.selectedSourceFieldLabel = undefined;
        this.selectedSourceFieldAPIName = undefined;
        this.selectedTargetFieldLabel = undefined;
        this.selectedTargetFieldAPIName = undefined;
    }

    /*******************************************************************************
    * @description Handles the close modal event from bdiFieldMappings
    */
    handleCloseModal() {
        this.isModalOpen = false;
    }

    /*******************************************************************************
    * @description Handles escape key press and closes the modal
    */
    escapeFunction(event) {
        if (event.keyCode === 27) {
            this.handleCloseModal();
        }
    }

    /*******************************************************************************
    * @description Handles the creation or update of a row based on the existence of
    * the row property. If the row property exists, we only set the source and target
    * field API name, otherwise we set all the fields. Calls the handleDeploymentId
    * function on receiving an id back from createDataImportFieldMapping.
    */
    handleSave() {
        let rowDetails;

        if (this.row) {
            // Set source and target fields
            this.row.xxx_Source_Field_API_Name_xxx = this.selectedSourceFieldAPIName;
            this.row.xxx_Target_Field_API_Name_xxx = this.selectedTargetFieldAPIName;
            rowDetails = JSON.stringify(this.row);
        } else {
            // New Field Mapping
            rowDetails = JSON.stringify({
                DeveloperName: null,
                Label: this.selectedSourceFieldLabel,
                MasterLabel: this.selectedSourceFieldLabel,
                xxx_Data_Import_Field_Mapping_Set_xxx: this.fieldMappingSetName,
                xxx_Is_Deleted_xxx: false,
                xxx_Required_xxx: 'No',
                xxx_Source_Field_API_Name_xxx: this.selectedSourceFieldAPIName,
                xxx_Target_Field_API_Name_xxx: this.selectedTargetFieldAPIName,
                xxx_Target_Object_Mapping_xxx: this.objectMapping.DeveloperName
            });
        }

        let missingField = this.handleFieldValidations();

        if (missingField.length === 0) {
            this.isLoading = true;
            createDataImportFieldMapping({fieldMappingString: rowDetails})
                .then((deploymentId) => {
                    this.handleDeploymentId(deploymentId);
                })
                .catch((error) => {
                    this.isLoading = false;
                    if (error && error.body) {
                        this.showToast(
                            'Error',
                            '{0}. {1}. {2}.',
                            'error',
                            'sticky',
                            [error.body.exceptionType, error.body.message, error.body.stackTrace]);
                    }
                });
        } else {
            this.showToast(
                'Error',
                `Missing the following fields [${missingField}]`,
                'error',
                'dismissable');
        }
    }

    /*******************************************************************************
    * @description Adds error classes to comboboxes and returns a string indicating
    * which combobox selection is incomplete.
    *
    * @return {string} missingFields: Name of the incomplete combobox
    */
    handleFieldValidations() {
        let missingField = '';
        if (!this.selectedSourceFieldAPIName) {
            missingField = 'Source Field';
            this.hasSourceFieldErrors = true;
        }
        if (!this.selectedTargetFieldAPIName && !this.isTargetFieldDisabled) {
            missingField = 'Target Field';
            this.hasTargetFieldErrors = true;
        }

        return missingField;
    }

    /*******************************************************************************
    * @description Creates and dispatches a CustomEvent 'deployment' letting the
    * platformEventListener know that we have an id to register and monitor. After
    * dispatching the CustomEvent, start the deployment timeout on bdiFieldMappings.
    *
    * @param {string} deploymentId: Custom Metadata Deployment Id
    */
    handleDeploymentId(deploymentId) {
        const deploymentEvent = new CustomEvent('deployment', {
            bubbles: true,
            composed: true,
            detail: {deploymentId}
        });
        this.dispatchEvent(deploymentEvent);

        fireEvent(this.pageRef, 'startDeploymentTimeout', { deploymentId: deploymentId });
    }

    /*******************************************************************************
    * @description Handles the onchange event for the bdiFieldMappingModalComboboxSearch
    * sourceFieldLabel, sets the value for both sourceFieldLabel and sourceFieldAPIName,
    * and updates the available target fields based on the selected source field's display
    * type.
    *
    * @param {object} event: Event containing combobox selection details
    */
    handleSourceFieldLabelChange(event) {
        let fieldAPIName = event.detail.value;
        let fieldInfo = this.diFieldsByAPIName[fieldAPIName];
        this.selectedSourceFieldLabel = fieldInfo.label;
        this.selectedSourceFieldAPIName = fieldAPIName;
        this.selectedSourceFieldDisplayType = this.toTitleCase(fieldInfo.displayType);
        this.selectedTargetFieldAPIName = undefined;

        this.handleAvailableTargetFieldsBySourceFieldDisplayType(this.selectedSourceFieldDisplayType);
    }

    /*******************************************************************************
    * @description Filters the available target fields based on display type
    *
    * @param {string} displayType: Display Type of the currently selected source field
    */
    handleAvailableTargetFieldsBySourceFieldDisplayType(displayType) {
        this.targetFieldLabelOptions = [];
        let validTargetTypes = this.validTargetTypesBySourceType[displayType];

        for (let i = 0; i < validTargetTypes.length; i++) {
            let validType = validTargetTypes[i];
            let validTargetTypesByLabel = this.targetFieldsByLabelByDisplayType[validType] || [];

            this.targetFieldLabelOptions.push(...validTargetTypesByLabel);
        }
    }

    /*******************************************************************************
    * @description Handles the onchange event for the bdiFieldMappingModalComboboxSearch
    * targetFieldLabel, sets the value for both targetFieldLabel and targetFieldAPIName.
    *
    * @param {object} event: Event containing combobox selection details
    */
    handleTargetFieldLabelChange(event) {
        this.selectedTargetFieldAPIName = event.detail.value;
        let fieldInfo = this.targetFieldsByAPIName[this.selectedTargetFieldAPIName];
        this.selectedTargetFieldLabel = fieldInfo.label;
        this.selectedTargetFieldDisplayType = this.toTitleCase(fieldInfo.displayType);
    }

    /*******************************************************************************
    * @description Creates and dispatches a ShowToastEvent
    *
    * @param {string} title: Title of the toast, dispalyed as a heading.
    * @param {string} message: Message of the toast. It can contain placeholders in
    * the form of {0} ... {N}. The placeholders are replaced with the links from
    * messageData param
    * @param {string} mode: Mode of the toast
    * @param {array} messageData: List of values that replace the {index} placeholders
    * in the message param
    */
    showToast(title, message, variant, mode, messageData) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode,
            messageData: messageData
        });
        this.dispatchEvent(event);
    }

    /*******************************************************************************
    * @description Parse proxy objects for debugging, mutating, etc
    *
    * @param {object} obj: Object to be parsed
    */
    parse(obj) {
       return JSON.parse(JSON.stringify(obj));
    }

    /*******************************************************************************
    * @description Title cases a string
    *
    * @param {string} string: String to be title cased
    */
    toTitleCase(string) {
        string = string.toLowerCase().split(' ');
        for (let i = 0; i < string.length; i++) {
            string[i] = string[i].charAt(0).toUpperCase() + string[i].slice(1);
        }
        return string.join(' ');
    }
}<|endoftext|>


<commit_before>var fs = require('fs'),
    assert = require('assert'),
    MiniLog = require('../lib/index.js'),
    Stringifier = require('../lib/node/stringify.js'),
    Transform = require('../lib/common/transform.js');

function WriteStream() {
  var self = this;
  this.writable = true;
  this.content = [];
}

Transform.mixin(WriteStream);

WriteStream.prototype.write = function(string, encoding) {
  this.content.push(string);
};

WriteStream.prototype.end = function() {};

exports['given a minilog'] = {

  beforeEach: function() {
    this.stream = new WriteStream();
    this.pipe = MiniLog.pipe(new Stringifier()).pipe(this.stream);
    this.log = MiniLog();
  },

  afterEach: function() {
    MiniLog.end();
  },

  'can log to process.stdout': function() {
    MiniLog.pipe(process.stdout);
    this.log('foo');
  },

  'can log to stream': function() {
    this.log('foo');
    assert.equal(this.stream.content[0], 'foo\n');
  },

  'can pass multiple arguments': function() {
    this.log('foo', 1, true, 'bar');
    assert.equal(this.stream.content[0], 'foo 1 true bar\n');
  },

  'can pass object as argument and get JSON': function() {
    this.log('foo', { bar: 'baz' });
    assert.equal(this.stream.content[0], 'foo {"bar":"baz"}\n');
    this.log([1, 2, { bar: 'baz'}]);
    assert.equal(this.stream.content[1], '[1,2,{"bar":"baz"}]\n');
  },

  'can use log.error, log.warn, log.info, log.debug, log.log': function() {
    this.log.error('aaa');
    assert.equal(this.stream.content[0], 'error aaa\n');
    this.log.warn('aaa');
    assert.equal(this.stream.content[1], 'warn aaa\n');
    this.log.info('aaa');
    assert.equal(this.stream.content[2], 'info aaa\n');
    this.log.debug('aaa');
    assert.equal(this.stream.content[3], 'debug aaa\n');
    this.log.log('aaa');
    assert.equal(this.stream.content[3], 'debug aaa\n');
  },

  'can create a namespaced logger': function() {
    this.log = MiniLog('ns');
    this.log('foo');
    assert.equal(this.stream.content[0], 'ns foo\n');
    this.log.error('aaa');
    assert.equal(this.stream.content[1], 'ns error aaa\n');
    this.log.warn('aaa');
    assert.equal(this.stream.content[2], 'ns warn aaa\n');
    this.log.info('aaa');
    assert.equal(this.stream.content[3], 'ns info aaa\n');
    this.log.debug('aaa');
    assert.equal(this.stream.content[4], 'ns debug aaa\n');
  },

  'can filter by namespace and type': function() {
    var ns = MiniLog('ns'),
        ns2 = MiniLog('ns2');

    var filter = new Transform();

    filter.write = function(name, level, args) {
      if(name == 'ns2' && (level == 'info' || level == 'error')) {
        filter.emit('item', name, level, args);
      }
    };

    MiniLog.unpipe();
    this.pipe = MiniLog.pipe(filter).pipe(new Stringifier()).pipe(this.stream);

    ns('foo');
    ns2('abc');
    ns2.info('cde');
    ns('bar');
    ns2.error('fgh');
    assert.equal(this.stream.content[0], 'ns2 info cde\n');
    assert.equal(this.stream.content[1], 'ns2 error fgh\n');
  },

  'can pipe into file': function(done) {
    var ws = fs.createWriteStream(__dirname + '/temp.log');
    MiniLog.pipe(ws);
    this.log('hello world');
    this.log('foobar');
    MiniLog.end();
    setTimeout(function() {
      assert.equal(fs.readFileSync(__dirname + '/temp.log').toString(), 'hello world\nfoobar\n');
      done();
    }, 10);
  },

  'can format logs': function(done) {
    MiniLog.unpipe();

    var format = new Transform();

    format.write = function(name, level, args) {
      this.emit('item', (name ? name.toUpperCase() + ' - ' : '')
           + (level ? level.toUpperCase() + ' - ' : '')
           + args.join(' ') +'\n');
    };

    this.pipe = MiniLog.pipe(format).pipe(this.stream);

    this.log.error('aaa');
    this.log.warn('aaa');
    this.log.info('aaa');
    assert.equal(this.stream.content[0], 'ERROR - aaa\n');
    assert.equal(this.stream.content[1], 'WARN - aaa\n');
    assert.equal(this.stream.content[2], 'INFO - aaa\n');
    done();
  },

  'logging a buffer doesn\'t look horrible': function() {
    this.log(new Buffer('Hello world'));
    assert.equal(this.stream.content[0], 'Hello world\n');
  }

};

// if this module is the script being run, then run the tests:
if (module == require.main) {
  var mocha = require('child_process').spawn('../node_modules/.bin/mocha', [ '--colors', '--ui', 'exports', '--reporter', 'spec', __filename ]);
  mocha.stdout.pipe(process.stdout);
  mocha.stderr.pipe(process.stderr);
}
<commit_msg>Fix unit test (never trust a test you dind't see fail)<commit_after>var fs = require('fs'),
    assert = require('assert'),
    MiniLog = require('../lib/index.js'),
    Stringifier = require('../lib/node/stringify.js'),
    Transform = require('../lib/common/transform.js');

function WriteStream() {
  var self = this;
  this.writable = true;
  this.content = [];
}

Transform.mixin(WriteStream);

WriteStream.prototype.write = function(string, encoding) {
  this.content.push(string);
};

WriteStream.prototype.end = function() {};

exports['given a minilog'] = {

  beforeEach: function() {
    this.stream = new WriteStream();
    this.pipe = MiniLog.pipe(new Stringifier()).pipe(this.stream);
    this.log = MiniLog();
  },

  afterEach: function() {
    MiniLog.end();
  },

  'can log to process.stdout': function() {
    MiniLog.pipe(process.stdout);
    this.log('foo');
  },

  'can log to stream': function() {
    this.log('foo');
    assert.equal(this.stream.content[0], 'foo\n');
  },

  'can pass multiple arguments': function() {
    this.log('foo', 1, true, 'bar');
    assert.equal(this.stream.content[0], 'foo 1 true bar\n');
  },

  'can pass object as argument and get JSON': function() {
    this.log('foo', { bar: 'baz' });
    assert.equal(this.stream.content[0], 'foo {"bar":"baz"}\n');
    this.log([1, 2, { bar: 'baz'}]);
    assert.equal(this.stream.content[1], '[1,2,{"bar":"baz"}]\n');
  },

  'can use log.error, log.warn, log.info, log.debug, log.log': function() {
    this.log.error('aaa');
    assert.equal(this.stream.content[0], 'error aaa\n');
    this.log.warn('aaa');
    assert.equal(this.stream.content[1], 'warn aaa\n');
    this.log.info('aaa');
    assert.equal(this.stream.content[2], 'info aaa\n');
    this.log.debug('aaa');
    assert.equal(this.stream.content[3], 'debug aaa\n');
    this.log.log('aaa');
    assert.equal(this.stream.content[4], 'debug aaa\n');
  },

  'can create a namespaced logger': function() {
    this.log = MiniLog('ns');
    this.log('foo');
    assert.equal(this.stream.content[0], 'ns foo\n');
    this.log.error('aaa');
    assert.equal(this.stream.content[1], 'ns error aaa\n');
    this.log.warn('aaa');
    assert.equal(this.stream.content[2], 'ns warn aaa\n');
    this.log.info('aaa');
    assert.equal(this.stream.content[3], 'ns info aaa\n');
    this.log.debug('aaa');
    assert.equal(this.stream.content[4], 'ns debug aaa\n');
  },

  'can filter by namespace and type': function() {
    var ns = MiniLog('ns'),
        ns2 = MiniLog('ns2');

    var filter = new Transform();

    filter.write = function(name, level, args) {
      if(name == 'ns2' && (level == 'info' || level == 'error')) {
        filter.emit('item', name, level, args);
      }
    };

    MiniLog.unpipe();
    this.pipe = MiniLog.pipe(filter).pipe(new Stringifier()).pipe(this.stream);

    ns('foo');
    ns2('abc');
    ns2.info('cde');
    ns('bar');
    ns2.error('fgh');
    assert.equal(this.stream.content[0], 'ns2 info cde\n');
    assert.equal(this.stream.content[1], 'ns2 error fgh\n');
  },

  'can pipe into file': function(done) {
    var ws = fs.createWriteStream(__dirname + '/temp.log');
    MiniLog.pipe(ws);
    this.log('hello world');
    this.log('foobar');
    MiniLog.end();
    setTimeout(function() {
      assert.equal(fs.readFileSync(__dirname + '/temp.log').toString(), 'hello world\nfoobar\n');
      done();
    }, 10);
  },

  'can format logs': function(done) {
    MiniLog.unpipe();

    var format = new Transform();

    format.write = function(name, level, args) {
      this.emit('item', (name ? name.toUpperCase() + ' - ' : '')
           + (level ? level.toUpperCase() + ' - ' : '')
           + args.join(' ') +'\n');
    };

    this.pipe = MiniLog.pipe(format).pipe(this.stream);

    this.log.error('aaa');
    this.log.warn('aaa');
    this.log.info('aaa');
    assert.equal(this.stream.content[0], 'ERROR - aaa\n');
    assert.equal(this.stream.content[1], 'WARN - aaa\n');
    assert.equal(this.stream.content[2], 'INFO - aaa\n');
    done();
  },

  'logging a buffer doesn\'t look horrible': function() {
    this.log(new Buffer('Hello world'));
    assert.equal(this.stream.content[0], 'Hello world\n');
  }

};

// if this module is the script being run, then run the tests:
if (module == require.main) {
  var mocha = require('child_process').spawn('../node_modules/.bin/mocha', [ '--colors', '--ui', 'exports', '--reporter', 'spec', __filename ]);
  mocha.stdout.pipe(process.stdout);
  mocha.stderr.pipe(process.stderr);
}
<|endoftext|>


<commit_before>import assert from 'assert';
import { Font, Path, Glyph, load, loadSync } from '../src/opentype';

describe('opentype.js', function() {
    it('can load a TrueType font', function() {
        const font = loadSync('./fonts/Roboto-Black.ttf');
        assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
        assert.equal(font.unitsPerEm, 2048);
        assert.equal(font.glyphs.length, 1294);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 15);
    });

    it('can load a OpenType/CFF font', function() {
        const font = loadSync('./fonts/FiraSansOT-Medium.otf');
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT Medium'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 1151);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('can load a CID-keyed font', function() {
        const font = loadSync('./fonts/FDArrayTest257.otf');
        assert.deepEqual(font.names.fontFamily, {en: 'FDArray Test 257'});
        assert.deepEqual(font.tables.cff.topDict.ros, ['Adobe', 'Identity', 0]);
        assert.equal(font.tables.cff.topDict._fdArray.length, 256);
        assert.equal(font.tables.cff.topDict._fdSelect[0], 0);
        assert.equal(font.tables.cff.topDict._fdSelect[42], 41);
        assert.equal(font.tables.cff.topDict._fdSelect[256], 255);
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 257);
        const aGlyph = font.glyphs.get(2);
        assert.equal(aGlyph.name, 'gid2');
        assert.equal(aGlyph.unicode, 1);
        assert.equal(aGlyph.path.commands.length, 24);
    });

    it('can load a WOFF/CFF font', function() {
        const font = loadSync('./fonts/FiraSansMedium.woff');
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 1147);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('handles a parseBuffer error', function(done) {
        load('./fonts/badfont.ttf', function(err) {
            if (err) {
                done();
            }
        });
    });

    it('throws an error when advanceWidth is not set', function() {
        const notdefGlyph = new Glyph({
            name: '.notdef',
            unicode: 0,
            path: new Path()
        });
        const font = new Font({
            familyName: 'MyFont',
            styleName: 'Medium',
            unitsPerEm: 1000,
            ascender: 800,
            descender: -200,
            glyphs: [notdefGlyph]
        });
        assert.throws(function() { font.toArrayBuffer(); }, /advanceWidth is not a number/);
    });
});

describe('opentype.js on low memory mode', function() {
    const opt = { lowMemory: true };

    it('can load a TrueType font', function() {
        const font = loadSync('./fonts/Roboto-Black.ttf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
        assert.equal(font.unitsPerEm, 2048);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 15);
    });

    it('can load a OpenType/CFF font', function() {
        const font = loadSync('./fonts/FiraSansOT-Medium.otf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT Medium'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('can load a CID-keyed font', function() {
        const font = loadSync('./fonts/FDArrayTest257.otf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'FDArray Test 257'});
        assert.deepEqual(font.tables.cff.topDict.ros, ['Adobe', 'Identity', 0]);
        assert.equal(font.tables.cff.topDict._fdArray.length, 256);
        assert.equal(font.tables.cff.topDict._fdSelect[0], 0);
        assert.equal(font.tables.cff.topDict._fdSelect[42], 41);
        assert.equal(font.tables.cff.topDict._fdSelect[256], 255);
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.glyphs.get(2);
        assert.equal(aGlyph.name, 'gid2');
        assert.equal(aGlyph.unicode, 1);
        assert.equal(aGlyph.path.commands.length, 24);
    });

    it('can load a WOFF/CFF font', function() {
        const font = loadSync('./fonts/FiraSansMedium.woff', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('handles a parseBuffer error', function(done) {
        load('./fonts/badfont.ttf', function(err) {
            if (err) {
                done();
            }
        });
    }, opt);

    it('throws an error when advanceWidth is not set', function() {
        const notdefGlyph = new Glyph({
            name: '.notdef',
            unicode: 0,
            path: new Path()
        });
        const font = new Font({
            familyName: 'MyFont',
            styleName: 'Medium',
            unitsPerEm: 1000,
            ascender: 800,
            descender: -200,
            glyphs: [notdefGlyph]
        });
        assert.throws(function() { font.toArrayBuffer(); }, /advanceWidth is not a number/);
    });
});
<commit_msg>Add promise-based tests for load<commit_after>import assert from 'assert';
import { Font, Path, Glyph, load, loadSync } from '../src/opentype';

describe('opentype.js', function() {
    it('can load a TrueType font', function() {
        const font = loadSync('./fonts/Roboto-Black.ttf');
        assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
        assert.equal(font.unitsPerEm, 2048);
        assert.equal(font.glyphs.length, 1294);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 15);
    });

    it('can load a TrueType font as a resolved promise', function(done) {
        load('./fonts/Roboto-Black.ttf').then((font) => {
            assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
            assert.equal(font.unitsPerEm, 2048);
            assert.equal(font.glyphs.length, 1294);
            const aGlyph = font.charToGlyph('A');
            assert.equal(aGlyph.unicode, 65);
            assert.equal(aGlyph.path.commands.length, 15);
            done();
        });
    });

    it('can load a OpenType/CFF font', function() {
        const font = loadSync('./fonts/FiraSansOT-Medium.otf');
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT Medium'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 1151);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('can load a CID-keyed font', function() {
        const font = loadSync('./fonts/FDArrayTest257.otf');
        assert.deepEqual(font.names.fontFamily, {en: 'FDArray Test 257'});
        assert.deepEqual(font.tables.cff.topDict.ros, ['Adobe', 'Identity', 0]);
        assert.equal(font.tables.cff.topDict._fdArray.length, 256);
        assert.equal(font.tables.cff.topDict._fdSelect[0], 0);
        assert.equal(font.tables.cff.topDict._fdSelect[42], 41);
        assert.equal(font.tables.cff.topDict._fdSelect[256], 255);
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 257);
        const aGlyph = font.glyphs.get(2);
        assert.equal(aGlyph.name, 'gid2');
        assert.equal(aGlyph.unicode, 1);
        assert.equal(aGlyph.path.commands.length, 24);
    });

    it('can load a WOFF/CFF font', function() {
        const font = loadSync('./fonts/FiraSansMedium.woff');
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 1147);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('handles a parseBuffer error', function(done) {
        load('./fonts/badfont.ttf', function(err) {
            if (err) {
                done();
            }
        });
    });

    it('handles a parseBuffer error as a rejected promise', function(done) {
        load('./fonts/badfont.ttf')
            .catch((err) => {
                if (err) {
                    done();
                }
            });
    });

    it('throws an error when advanceWidth is not set', function() {
        const notdefGlyph = new Glyph({
            name: '.notdef',
            unicode: 0,
            path: new Path()
        });
        const font = new Font({
            familyName: 'MyFont',
            styleName: 'Medium',
            unitsPerEm: 1000,
            ascender: 800,
            descender: -200,
            glyphs: [notdefGlyph]
        });
        assert.throws(function() { font.toArrayBuffer(); }, /advanceWidth is not a number/);
    });
});

describe('opentype.js on low memory mode', function() {
    const opt = { lowMemory: true };

    it('can load a TrueType font', function() {
        const font = loadSync('./fonts/Roboto-Black.ttf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
        assert.equal(font.unitsPerEm, 2048);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 15);
    });

    it('can load a OpenType/CFF font', function() {
        const font = loadSync('./fonts/FiraSansOT-Medium.otf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT Medium'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('can load a CID-keyed font', function() {
        const font = loadSync('./fonts/FDArrayTest257.otf', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'FDArray Test 257'});
        assert.deepEqual(font.tables.cff.topDict.ros, ['Adobe', 'Identity', 0]);
        assert.equal(font.tables.cff.topDict._fdArray.length, 256);
        assert.equal(font.tables.cff.topDict._fdSelect[0], 0);
        assert.equal(font.tables.cff.topDict._fdSelect[42], 41);
        assert.equal(font.tables.cff.topDict._fdSelect[256], 255);
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.glyphs.get(2);
        assert.equal(aGlyph.name, 'gid2');
        assert.equal(aGlyph.unicode, 1);
        assert.equal(aGlyph.path.commands.length, 24);
    });

    it('can load a WOFF/CFF font', function() {
        const font = loadSync('./fonts/FiraSansMedium.woff', opt);
        assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT'});
        assert.equal(font.unitsPerEm, 1000);
        assert.equal(font.glyphs.length, 0);
        const aGlyph = font.charToGlyph('A');
        assert.equal(aGlyph.name, 'A');
        assert.equal(aGlyph.unicode, 65);
        assert.equal(aGlyph.path.commands.length, 14);
    });

    it('handles a parseBuffer error', function(done) {
        load('./fonts/badfont.ttf', function(err) {
            if (err) {
                done();
            }
        });
    }, opt);

    it('throws an error when advanceWidth is not set', function() {
        const notdefGlyph = new Glyph({
            name: '.notdef',
            unicode: 0,
            path: new Path()
        });
        const font = new Font({
            familyName: 'MyFont',
            styleName: 'Medium',
            unitsPerEm: 1000,
            ascender: 800,
            descender: -200,
            glyphs: [notdefGlyph]
        });
        assert.throws(function() { font.toArrayBuffer(); }, /advanceWidth is not a number/);
    });
});
<|endoftext|>


<commit_before>const assert = require('assert');
const pack = require('../package.json');
const exec = require('child_process');

suite('Package', () => {
  test('git tag must match package version', function() {
    const tag = exec.execSync('git tag -l --contains HEAD').toString().trim();
    if (tag === '') {
      console.log('    No git tag, no need to check tag!');
      this.skip();
    }
    assert.equal('v' + pack.version, tag);
  });

});
<commit_msg>Remove package_test, as it's less useful when releasing via push<commit_after><|endoftext|>


<commit_before>var vows = require('vows'),
	assert = require('assert'),
	path = require('path')
program = require('../');

function importFile(filename, argv) {
	argv = argv || [];
	argv.push('--config=' + filename);
	var app = program;
	app.setConfigFile('config');
	return app.parse(argv);
}

var fixtures = path.join(__dirname, 'fixtures');

vows.describe('config file parsing').addBatch({
	'should parse a .yaml file': {
		topic: importFile(path.join(fixtures, 'config.yaml')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'yaml');
		}
	},
	'should parse a .yml file': {
		topic: importFile(path.join(fixtures, 'config.yml')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'yml');
		}
	},
	'should parse a .json file': {
		topic: importFile(path.join(fixtures, 'config.json')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'json');
		}
	},
	'should accept command line options': {
		topic: importFile(path.join(fixtures, 'config.json'), ['--format=bson', '--test=baz']),
		'config file options should take precendence over CLI flags': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'json');
		},
		'additional CLI options are merged in': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.test, 'baz');
		}
	},
	'should fail when trying to parse an unknown file': {
		topic: function() {
			try {
				importFile(path.join(fixtures, 'nonexistent.json'))
			} catch (err) {
				this.callback(err)
			}
		},
		'should throw an error': function(err, options) {
			assert.equal(err, 'Config file "' + path.join(fixtures, 'nonexistent.json') + '" cannot be found!')
		}
	},
	'should fail when trying to parse a broken file': {
		topic: function() {
			try {
				importFile(path.join(fixtures, 'broken.json'))
			} catch (err) {
				this.callback(err)
			}
		},
		'should throw an error': function(err, options) {
			assert.equal(err, 'JSON config file parsing failed (SyntaxError: Unexpected token t in JSON at position 4)')
		}
	}
}).export(module);
<commit_msg>Lint test/parsing-test.js<commit_after>var vows = require('vows'),
	assert = require('assert'),
	path = require('path'),
	program = require('../');

function importFile(filename, argv) {
	argv = argv || [];
	argv.push('--config=' + filename);
	var app = program;
	app.setConfigFile('config');
	return app.parse(argv);
}

var fixtures = path.join(__dirname, 'fixtures');

vows.describe('config file parsing').addBatch({
	'should parse a .yaml file': {
		topic: importFile(path.join(fixtures, 'config.yaml')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'yaml');
		}
	},
	'should parse a .yml file': {
		topic: importFile(path.join(fixtures, 'config.yml')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'yml');
		}
	},
	'should parse a .json file': {
		topic: importFile(path.join(fixtures, 'config.json')),
		'should move config into options': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'json');
		}
	},
	'should accept command line options': {
		topic: importFile(path.join(fixtures, 'config.json'), ['--format=bson', '--test=baz']),
		'config file options should take precendence over CLI flags': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.format, 'json');
		},
		'additional CLI options are merged in': function(err, options) {
			assert.equal(err, null);
			assert.equal(options.test, 'baz');
		}
	},
	'should fail when trying to parse an unknown file': {
		topic: function() {
			try {
				importFile(path.join(fixtures, 'nonexistent.json'));
			} catch (err) {
				this.callback(err);
			}
		},
		'should throw an error': function(err, options) {
			assert.equal(err, 'Config file "' + path.join(fixtures, 'nonexistent.json') + '" cannot be found!');
		}
	},
	'should fail when trying to parse a broken file': {
		topic: function() {
			try {
				importFile(path.join(fixtures, 'broken.json'));
			} catch (err) {
				this.callback(err);
			}
		},
		'should throw an error': function(err, options) {
			assert.equal(err, 'JSON config file parsing failed (SyntaxError: Unexpected token t in JSON at position 4)');
		}
	}
}).export(module);
<|endoftext|>


<commit_before>
var express = require('../')
  , request = require('./support/http')
  , assert = require('assert');

describe('res', function(){
  describe('.sendfile(path, fn)', function(){
    it('should invoke the callback when complete', function(done){
      var app = express();

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', function(err){
          assert(!err);
          req.socket.listeners('error').should.have.length(1); // node's original handler
          done();
        });
      });

      request(app)
      .get('/')
      .expect(200)
      .end(function(){});
    })

    it('should utilize the same options as express.static()', function(done){
      var app = express();

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', { maxAge: 60000 });
      });

      request(app)
      .get('/')
      .expect('Cache-Control', 'public, max-age=60')
      .end(done);
    })

    it('should invoke the callback on 404', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/nope.html', function(err){
          ++calls;
          assert(!res.headerSent);
          res.send(err.message);
        });
      });

      request(app)
      .get('/')
      .end(function(err, res){
        assert(1 == calls, 'called too many times');
        res.text.should.equal("ENOENT, stat 'test/fixtures/nope.html'");
        res.statusCode.should.equal(200);
        done();
      });
    })

    it('should not override manual content-types', function(done){
      var app = express();

      app.use(function(req, res){
        res.contentType('txt');
        res.sendfile('test/fixtures/user.html');
      });

      request(app)
      .get('/')
      .expect('Content-Type', 'text/plain')
      .end(done);
    })

    it('should invoke the callback on 403', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/foo/../user.html', function(err){
          assert(!res.headerSent);
          ++calls;
          res.send(err.message);
        });
      });

      request(app)
      .get('/')
      .expect('Forbidden')
      .expect(200, done);
    })

    it('should invoke the callback on socket error', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', function(err){
          assert(!res.headerSent);
          req.socket.listeners('error').should.have.length(1); // node's original handler
          done();
        });

        req.socket.emit('error', new Error('broken!'));
      });

      request(app)
      .get('/')
      .end(function(){});
    })
  })

  describe('.sendfile(path)', function(){
    describe('with an absolute path', function(){
      it('should transfer the file', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile(__dirname + '/fixtures/user.html');
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })
    })

    describe('with a relative path', function(){
      it('should transfer the file', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('test/fixtures/user.html');
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })

      it('should serve relative to "root"', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('user.html', { root: 'test/fixtures/' });
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })

      it('should consider ../ malicious when "root" is not set', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('test/fixtures/foo/../user.html');
        });

        request(app)
        .get('/')
        .expect(403, done);
      })

      it('should allow ../ when "root" is set', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('foo/../user.html', { root: 'test/fixtures' });
        });

        request(app)
        .get('/')
        .expect(200, done);
      })

      it('should disallow requesting out of "root"', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('foo/../../user.html', { root: 'test/fixtures' });
        });

        request(app)
        .get('/')
        .expect(403, done);
      })

      it('should next(404) when not found', function(done){
        var app = express()
          , calls = 0;

        app.use(function(req, res){
          res.sendfile('user.html');
        });

        app.use(function(req, res){
          assert(0, 'this should not be called');
        });

        app.use(function(err, req, res, next){
          ++calls;
          next(err);
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.statusCode.should.equal(404);
          calls.should.equal(1);
          done();
        });
      })

      describe('with non-GET', function(){
        it('should still serve', function(done){
          var app = express()
            , calls = 0;

          app.use(function(req, res){
            res.sendfile(__dirname + '/fixtures/name.txt');
          });

          request(app)
          .get('/')
          .expect('tobi', done);
        })
      })
    })
  })
})
<commit_msg>be less picky with ENOENT errors in tests<commit_after>
var express = require('../')
  , request = require('./support/http')
  , assert = require('assert');

describe('res', function(){
  describe('.sendfile(path, fn)', function(){
    it('should invoke the callback when complete', function(done){
      var app = express();

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', function(err){
          assert(!err);
          req.socket.listeners('error').should.have.length(1); // node's original handler
          done();
        });
      });

      request(app)
      .get('/')
      .expect(200)
      .end(function(){});
    })

    it('should utilize the same options as express.static()', function(done){
      var app = express();

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', { maxAge: 60000 });
      });

      request(app)
      .get('/')
      .expect('Cache-Control', 'public, max-age=60')
      .end(done);
    })

    it('should invoke the callback on 404', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/nope.html', function(err){
          ++calls;
          assert(!res.headerSent);
          res.send(err.message);
        });
      });

      request(app)
      .get('/')
      .end(function(err, res){
        assert(1 == calls, 'called too many times');
        res.text.should.startWith("ENOENT, stat");
        res.statusCode.should.equal(200);
        done();
      });
    })

    it('should not override manual content-types', function(done){
      var app = express();

      app.use(function(req, res){
        res.contentType('txt');
        res.sendfile('test/fixtures/user.html');
      });

      request(app)
      .get('/')
      .expect('Content-Type', 'text/plain')
      .end(done);
    })

    it('should invoke the callback on 403', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/foo/../user.html', function(err){
          assert(!res.headerSent);
          ++calls;
          res.send(err.message);
        });
      });

      request(app)
      .get('/')
      .expect('Forbidden')
      .expect(200, done);
    })

    it('should invoke the callback on socket error', function(done){
      var app = express()
        , calls = 0;

      app.use(function(req, res){
        res.sendfile('test/fixtures/user.html', function(err){
          assert(!res.headerSent);
          req.socket.listeners('error').should.have.length(1); // node's original handler
          done();
        });

        req.socket.emit('error', new Error('broken!'));
      });

      request(app)
      .get('/')
      .end(function(){});
    })
  })

  describe('.sendfile(path)', function(){
    describe('with an absolute path', function(){
      it('should transfer the file', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile(__dirname + '/fixtures/user.html');
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })
    })

    describe('with a relative path', function(){
      it('should transfer the file', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('test/fixtures/user.html');
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })

      it('should serve relative to "root"', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('user.html', { root: 'test/fixtures/' });
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.text.should.equal('
{{user.name}}
');
          res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
          done();
        });
      })

      it('should consider ../ malicious when "root" is not set', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('test/fixtures/foo/../user.html');
        });

        request(app)
        .get('/')
        .expect(403, done);
      })

      it('should allow ../ when "root" is set', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('foo/../user.html', { root: 'test/fixtures' });
        });

        request(app)
        .get('/')
        .expect(200, done);
      })

      it('should disallow requesting out of "root"', function(done){
        var app = express();

        app.use(function(req, res){
          res.sendfile('foo/../../user.html', { root: 'test/fixtures' });
        });

        request(app)
        .get('/')
        .expect(403, done);
      })

      it('should next(404) when not found', function(done){
        var app = express()
          , calls = 0;

        app.use(function(req, res){
          res.sendfile('user.html');
        });

        app.use(function(req, res){
          assert(0, 'this should not be called');
        });

        app.use(function(err, req, res, next){
          ++calls;
          next(err);
        });

        request(app)
        .get('/')
        .end(function(err, res){
          res.statusCode.should.equal(404);
          calls.should.equal(1);
          done();
        });
      })

      describe('with non-GET', function(){
        it('should still serve', function(done){
          var app = express()
            , calls = 0;

          app.use(function(req, res){
            res.sendfile(__dirname + '/fixtures/name.txt');
          });

          request(app)
          .get('/')
          .expect('tobi', done);
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>// assertion
var assert = require('assert'),
    should = require('should');

// logging
var LogConfig = require('../config/LogConfig');
LogConfig.configure('error');

// postgresql and roaster storage
var PGConn = require('../storage/postgre/PGConn'),
    PostgreRoasterStore = require('../storage/postgre/roaster/PostgreRoasterStore');

// user
var user = {
    jid: 'romeo@example.net'
};

var juliaAdd = {
    'jid' : 'julia@example.net',
    'name' : 'Julia',
    'subscription':'from',
    'group': ['Lovers']
};

var juliaUpdate = {
    'jid' : 'julia@example.net',
    'name' : 'Julia II',
    'subscription':'To',
    'group': ['Lovers', 'Friends' ]
};

var juliaNonExistent = {
    'jid' : 'julia2@example.net',
    'name' : 'Julia II',
    'subscription':'To',
    'group': ['Lovers', 'Friends' ]
}

describe('Roaster', function () {
    describe('Postgresql', function () {
        var roasterStorage;
        var initialroaster;

        before(function (done) {
            var pgConnectionString = process.env.DATABASE_URL;

            var pgC = new PGConn(pgConnectionString);
            pgC.connect(function () {
                roasterStorage = new PostgreRoasterStore({
                    client: pgC.getClient()
                });
                done();
            });
        });

        it('Get all roaster items', function (done) {

            roasterStorage.list(user.jid, function (err, roaster) {
                should.not.exist(err);
                assert.equal(roaster instanceof Array, true);
                assert.equal(roaster.length >= 0, true);
                initialroaster = roaster;
                done();
            });
        });

        it('Add a roaster item', function (done) {

            roasterStorage.add(user.jid, juliaAdd, function (err, item) {
                should.not.exist(err);
                assert.deepEqual(juliaAdd, item);
                done();
            });
        });

        it('Update a roaster item', function (done) {
            roasterStorage.update(user.jid, juliaUpdate, function (err, item) {
                should.not.exist(err);
                assert.deepEqual(juliaUpdate, item);
                done();
            });
        });

        it('Get one roaster item and compare it', function (done) {
            done();
        });

        it('Try to update a non-existent roaster item', function (done) {
            roasterStorage.update(user.jid, juliaNonExistent, function (err) {
                should.exist(err);
                done();
            });
        });

        it('Roaster should include the new entry', function (done) {
            roasterStorage.list(user.jid, function (err, roaster) {
                should.not.exist(err);
                assert.equal(roaster instanceof Array, true);
                assert.ok(roaster.length === initialroaster.length + 1);
                done();
            });
        });

        it('Delete a roaster item', function (done) {
            roasterStorage.delete(user.jid, juliaUpdate, function (err) {
                should.not.exist(err);
                done();
            });
        });

        it('Compate current roaster list with inital roaster list', function (done) {

            roasterStorage.list(user.jid, function (err, roaster) {
                should.not.exist(err);
                assert.equal(roaster instanceof Array, true);
                assert.deepEqual(roaster, initialroaster);
                assert.ok(roaster.length === initialroaster.length);
                done();
            });
        });
    });
});<commit_msg>remove roaster store test<commit_after><|endoftext|>