<commit_before>require([
  'esri/renderers/ClassBreaksRenderer',
  'dojo/_base/Color',
  'esri/layers/FeatureLayer',
  'esri/layers/LayerDrawingOptions',
  'esri/dijit/Legend',
  'esri/map',
  'esri/symbols/SimpleLineSymbol',
  'esri/symbols/SimpleMarkerSymbol',
  'dojo/promise/all',
  'dojo/_base/array',
  'dojo/parser',
  'esri/request',
  'dojo/domReady!'
],
function(
  ClassBreaksRenderer,
  Color,
  FeatureLayer,
  LayerDrawingOptions,
  Legend,
  Map,
  SimpleLineSymbol,
  SimpleMarkerSymbol,
  all,
  array,
  parser,
  request) {
  parser.parse();
  var AGG_URL = 'http://utility.arcgis.com/usrsvcs/servers/06fb34d68e7a4673ba885015b671387b/rest/services/CCRPC/SidewalkInventoryScoreAggregated/MapServer',
    AGG_DEFAULT_FEATURE = 'Sidewalk',
    AGG_DEFAULT_FIELD = 'ScoreCompliance',
    IND_URL = 'http://utility.arcgis.com/usrsvcs/servers/88a6a9f6dc45461f820659d2d0f13fff/rest/services/CCRPC/SidewalkInventoryScore/MapServer',
    MARKER_TYPE_LINE = 'line',
    BREAKS = [
      {
        maxValue: 60,
        label: '0 to 60',
        color: '#e61c1f'
      },
      {
        maxValue: 70,
        label: '> 60 to 70',
        color: '#ffb061'
      },
      {
        maxValue: 80,
        label: '> 70 to 80',
        color: '#fcfc5d'
      },
      {
        maxValue: 90,
        label: '> 80 to 90',
        color: '#80cee8'
      },
      {
        maxValue: 100,
        label: '> 90 to 100',
        color: '#2e80bf'
      }
    ],
    makeIndLayer = function(idx, markerType, markerSize, visible) {
      var layer = new FeatureLayer(IND_URL + '/' + idx, {
        mode: FeatureLayer.MODE_ONDEMAND,
        outFields: ['*']
      });
      layer.setScaleRange(10001, 0);
      layer.setRenderer(makeIndRenderer(markerType, markerSize));
      if (!visible) layer.hide();
      return layer;
    },
    makeIndRenderer = function(markerType, markerSize) {
      var renderer = new ClassBreaksRenderer(
        makeMarkerSymbol(markerType, markerSize, '#bbbbbb'),
        'ScoreCompliance');
      array.forEach(BREAKS, function(b, i) {
        renderer.addBreak(
          (i > 0) ? BREAKS[i-1].maxValue + 0.000001 : 0,
          b.maxValue,
          makeMarkerSymbol(markerType, markerSize, b.color));
      });
      return renderer;
    },
    makeMarkerSymbol = function(markerType, markerSize, fillColor) {
      if (markerType == MARKER_TYPE_LINE) {
        return new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SOLID,
          new Color(fillColor),
          markerSize
        );
      }
      return new SimpleMarkerSymbol(
        markerType,
        markerSize,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SOLID,
          new Color('#000000'),
          1
        ),
        new Color(fillColor)
      );
    },
    updateAggLayer = function() {
      aggRenderer.attributeField =
        fieldName.options[fieldName.selectedIndex].value;
      aggLayer.setRenderer(aggRenderer);
      aggLayer.redraw();
    },
    updateIndLayers = function() {
      var field = fieldName.options[fieldName.selectedIndex].value;
      console.log(field);
      array.forEach([swLayer, crLayer, cwLayer, psLayer], function(layer, i) {
        if (i == featureType.selectedIndex) {
          layer.renderer.attributeField = field;
          if (layer.visible) {
            layer.refresh();
          } else {
            layer.show();
          }
        } else {
          layer.hide();
        }
      });
    },
    initLegend = function() {
      var legend = new Legend({
        map: map,
        layerInfos: [{
          layer: aggLayer,
          title: 'Legend'
        }],
        autoUpdate: false
      }, document.getElementById('legend'));
      legend.startup();
    },
    populateFieldChoices = function(fields) {
      array.forEach(
          featureType.children,
          function(ftOption) {
        featureFields[ftOption.value] = [];
        array.forEach(fields, function(field) {
          if (
              field.name.substr(0, ftOption.value.length) == ftOption.value &&
              field.alias.substr(field.alias.length - 5) == 'Score') {
            featureFields[ftOption.value].push({
              value: field.name,
              label: field.alias.substr(ftOption.text.length)
            });
          }
        });
      });
    },
    populateFieldList = function(featureType) {
      while (fieldName.firstChild) fieldName.removeChild(fieldName.firstChild);
      array.forEach(featureFields[featureType], function(field, i) {
        var option = document.createElement('option');
        option.value = field.value;
        option.appendChild(document.createTextNode(
          field.label.substr(0, field.label.length - 6)));
        fieldName.appendChild(option);
        if (field.label == 'Compliance Score') fieldName.selectedIndex = i;
      });
    },
    requestLayerInfo = function(url) {
      return request({
        url: url,
        content: {
          f: 'json'
        },
        callbackParamName: 'callback'
      });
    },
    initFieldSelection = function(aggInfo) {
      // Set the minimum value for the renderer to 0.
      aggInfo.drawingInfo.renderer.minValue = 0;
      aggRenderer = new ClassBreaksRenderer(aggInfo.drawingInfo.renderer);

      // Populate the field choices object.
      populateFieldChoices(aggInfo.fields);

      // Set up the change handler for feature type.
      featureType.onchange = function() {
        populateFieldList(this.options[this.selectedIndex].value);
      };

      // Set the initial values of the feature type and field name selects.
      featureType.selectedIndex = 0;
      featureType.onchange();

      // Set up the click handler for the update map button.
      updateButton.onclick = function() {
        updateAggLayer();
        updateIndLayers();
      };
      updateButton.removeAttribute('disabled');
    },
    map = new Map('map', {
      center: [-88.2, 40.1],
      zoom: 11,
      basemap: 'gray-vector'
    }),
    aggLayer = new FeatureLayer(AGG_URL + '/0', {
      mode: FeatureLayer.MODE_SNAPSHOT,
      outFields: ['*'],
      opacity: 0.5
    }),
    aggRenderer,
    crLayer = makeIndLayer(0, SimpleMarkerSymbol.STYLE_CIRCLE, 10, false),
    cwLayer = makeIndLayer(1, SimpleMarkerSymbol.STYLE_SQUARE, 10, false),
    psLayer = makeIndLayer(2, SimpleMarkerSymbol.STYLE_DIAMOND, 10, false),
    swLayer = makeIndLayer(3, MARKER_TYPE_LINE, 3, true),
    fieldName = document.getElementById('fieldName'),
    featureType = document.getElementById('featureType'),
    updateButton = document.getElementById('updateMap'),
    featureFields = {};

    aggLayer.on('load', function(e) {
      initLegend();
      aggLayer.setScaleRange(0, 10000);
    });
    map.addLayers([aggLayer, crLayer, cwLayer, psLayer, swLayer]);

    // Request layer information.
    all([
      requestLayerInfo(IND_URL + '/0'),
      requestLayerInfo(IND_URL + '/1'),
      requestLayerInfo(IND_URL + '/2'),
      requestLayerInfo(IND_URL + '/3'),
      requestLayerInfo(AGG_URL + '/0')
    ]).then(function(res) {
      initFieldSelection(res[4]);
    });
});
<commit_msg>Improved field labeling.<commit_after>require([
  'esri/renderers/ClassBreaksRenderer',
  'dojo/_base/Color',
  'esri/layers/FeatureLayer',
  'esri/layers/LayerDrawingOptions',
  'esri/dijit/Legend',
  'esri/map',
  'esri/symbols/SimpleLineSymbol',
  'esri/symbols/SimpleMarkerSymbol',
  'dojo/promise/all',
  'dojo/_base/array',
  'dojo/parser',
  'esri/request',
  'dojo/domReady!'
],
function(
  ClassBreaksRenderer,
  Color,
  FeatureLayer,
  LayerDrawingOptions,
  Legend,
  Map,
  SimpleLineSymbol,
  SimpleMarkerSymbol,
  all,
  array,
  parser,
  request) {
  parser.parse();
  var AGG_URL = 'http://utility.arcgis.com/usrsvcs/servers/06fb34d68e7a4673ba885015b671387b/rest/services/CCRPC/SidewalkInventoryScoreAggregated/MapServer',
    AGG_DEFAULT_FEATURE = 'Sidewalk',
    AGG_DEFAULT_FIELD = 'ScoreCompliance',
    IND_URL = 'http://utility.arcgis.com/usrsvcs/servers/88a6a9f6dc45461f820659d2d0f13fff/rest/services/CCRPC/SidewalkInventoryScore/MapServer',
    MARKER_TYPE_LINE = 'line',
    BREAKS = [
      {
        maxValue: 60,
        label: '0 to 60',
        color: '#e61c1f'
      },
      {
        maxValue: 70,
        label: '> 60 to 70',
        color: '#ffb061'
      },
      {
        maxValue: 80,
        label: '> 70 to 80',
        color: '#fcfc5d'
      },
      {
        maxValue: 90,
        label: '> 80 to 90',
        color: '#80cee8'
      },
      {
        maxValue: 100,
        label: '> 90 to 100',
        color: '#2e80bf'
      }
    ],
    FIELDS = {
      Sidewalk: [
        {
          label: 'ADA Compliance',
          fields: [
            {
              label: 'Maximum Cross Slope',
              aggField: 'SidewalkScoreMaxCrossSlope',
              indField: 'ScoreMaxCrossSlope'
            },
            {
              label: 'Largest Vertical Fault',
              aggField: 'SidewalkScoreLargestVerticalFau',
              indField: 'ScoreLargestVerticalFault'
            },
            {
              label: 'Obstruction Types',
              aggField: 'SidewalkScoreObstructionTypes',
              indField: 'ScoreObstructionTypes'
            },
            {
              label: 'Width',
              aggField: 'SidewalkScoreCompliance',
              indField: 'ScoreWidth'
            },
            {
              label: 'Overall Compliance',
              aggField: 'SidewalkScoreWidth',
              indField: 'ScoreCompliance',
              isDefault: true
            }
          ]
        },
        {
          label: 'Condition',
          fields: [
            {
              label: 'Surface Condition',
              aggField: 'SidewalkScoreSurfaceCondition',
              indField: 'ScoreSurfaceCondition'
            },
            {
              label: 'Vertical Fault Count',
              aggField: 'SidewalkScoreVerticalFaultCount',
              indField: 'ScoreVerticalFaultCount'
            },
            {
              label: 'Cracked Panels',
              aggField: 'SidewalkScoreCrackedPanelCount',
              indField: 'ScoreCrackedPanelCount'
            },
            {
              label: 'Overall Condition',
              aggField: 'SidewalkScoreCondition',
              indField: 'ScoreCondition'
            }
          ]
        }
      ],
      CurbRamp: [
        {
          label: 'ADA Compliance',
          fields: [
            {
               label: 'Ramp Width',
               aggField: 'CurbRampScoreRampWidth',
               indField: 'ScoreRampWidth'
            },
            {
               label: 'Ramp Cross Slope',
               aggField: 'CurbRampScoreRampCrossSlope',
               indField: 'ScoreRampCrossSlope'
            },
            {
               label: 'Ramp Running Slope',
               aggField: 'CurbRampScoreRampRunningSlope',
               indField: 'ScoreRampRunningSlope'
            },
            {
               label: 'Detectable Warning Type',
               aggField: 'CurbRampScoreDetectableWarningT',
               indField: 'ScoreDetectableWarningType'
            },
            {
               label: 'Detectable Warning Width',
               aggField: 'CurbRampScoreDetectableWarningW',
               indField: 'ScoreDetectableWarningWidth'
            },
            {
               label: 'Gutter Cross Slope',
               aggField: 'CurbRampScoreGutterCrossSlope',
               indField: 'ScoreGutterCrossSlope'
            },
            {
               label: 'Gutter Running Slope',
               aggField: 'CurbRampScoreGutterRunningSlope',
               indField: 'ScoreGutterRunningSlope'
            },
            {
               label: 'Landing Dimensions',
               aggField: 'CurbRampScoreLandingDimensions',
               indField: 'ScoreLandingDimensions'
            },
            {
               label: 'Landing Slope',
               aggField: 'CurbRampScoreLandingSlope',
               indField: 'ScoreLandingSlope'
            },
            {
               label: 'Approach Cross Slope',
               aggField: 'CurbRampScoreApproachCrossSlope',
               indField: 'ScoreApproachCrossSlope'
            },
            {
               label: 'Flare Slope',
               aggField: 'CurbRampScoreFlareSlope',
               indField: 'ScoreFlareSlope'
            },
            {
               label: 'Largest Vertical Fault',
               aggField: 'CurbRampScoreLargestPavementFau',
               indField: 'ScoreLargestPavementFault'
            },
            {
               label: 'Obstruction',
               aggField: 'CurbRampScoreObstruction',
               indField: 'ScoreObstruction'
            },
            {
               label: 'Overall Compliance',
               aggField: 'CurbRampScoreCompliance',
               indField: 'ScoreCompliance'
            }
          ]
        },
        {
          label: 'Condition',
          fields: [
            {
               label: 'Surface Condition',
               aggField: 'CurbRampScoreSurfaceCondition',
               indField: 'ScoreSurfaceCondition'
            },
            {
               label: 'Vertical Fault Count',
               aggField: 'CurbRampScorePavementFaultCount',
               indField: 'ScorePavementFaultCount'
            },
            {
               label: 'Cracked Panel Count',
               aggField: 'CurbRampScoreCrackedPanelCount',
               indField: 'ScoreCrackedPanelCount'
            },
            {
               label: 'Overall Condition',
               aggField: 'CurbRampScoreCondition',
               indField: 'ScoreCondition'
            },
          ]
        }
      ],
      Crosswalk: [
        {
          label: 'ADA Compliance',
          fields: [
            {
               label: 'Width',
               aggField: 'CrosswalkScoreWidth',
               indField: 'ScoreWidth'
            },
            {
               label: 'Cross Slope',
               aggField: 'CrosswalkScoreCrossSlope',
               indField: 'ScoreCrossSlope'
            },
            {
               label: 'Overall Compliance',
               aggField: 'CrosswalkScoreCompliance',
               indField: 'ScoreCompliance'
            }
          ]
        }
      ],
      PedestrianSignal: [
        {
          label: 'ADA Compliance',
          fields: [
            {
               label: 'Button Size',
               aggField: 'PedestrianSignalScoreButtonSize',
               indField: 'ScoreButtonSize'
            },
            {
               label: 'Button Height',
               aggField: 'PedestrianSignalScoreButtonHeig',
               indField: 'ScoreButtonHeight'
            },
            {
               label: 'Button Position and Appearance',
               aggField: 'PedestrianSignalScoreButtonPosi',
               indField: 'ScoreButtonPositionAppearance'
            },
            {
               label: 'Tactile Features',
               aggField: 'PedestrianSignalScoreTactileFea',
               indField: 'ScoreTactileFeatures'
            },
            {
               label: 'Overall Compliance',
               aggField: 'PedestrianSignalScoreCompliance',
               indField: 'ScoreCompliance'
            }
          ]
        }
      ]
    },
    makeIndLayer = function(idx, markerType, markerSize, visible) {
      var layer = new FeatureLayer(IND_URL + '/' + idx, {
        mode: FeatureLayer.MODE_ONDEMAND,
        outFields: ['*']
      });
      layer.setScaleRange(10001, 0);
      layer.setRenderer(makeIndRenderer(markerType, markerSize));
      if (!visible) layer.hide();
      return layer;
    },
    makeIndRenderer = function(markerType, markerSize) {
      var renderer = new ClassBreaksRenderer(
        makeMarkerSymbol(markerType, markerSize, '#bbbbbb'),
        'ScoreCompliance');
      array.forEach(BREAKS, function(b, i) {
        renderer.addBreak(
          (i > 0) ? BREAKS[i-1].maxValue + 0.000001 : 0,
          b.maxValue,
          makeMarkerSymbol(markerType, markerSize, b.color));
      });
      return renderer;
    },
    makeMarkerSymbol = function(markerType, markerSize, fillColor) {
      if (markerType == MARKER_TYPE_LINE) {
        return new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SOLID,
          new Color(fillColor),
          markerSize
        );
      }
      return new SimpleMarkerSymbol(
        markerType,
        markerSize,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SOLID,
          new Color('#000000'),
          1
        ),
        new Color(fillColor)
      );
    },
    updateAggLayer = function(selectedField) {
      aggRenderer.attributeField = selectedField.aggField;
      aggLayer.setRenderer(aggRenderer);
      aggLayer.redraw();
    },
    updateIndLayers = function(selectedField) {
      array.forEach([swLayer, crLayer, cwLayer, psLayer], function(layer, i) {
        if (i == featureTypeSelect.selectedIndex) {
          layer.renderer.attributeField = selectedField.indField;
          if (layer.visible) {
            layer.refresh();
          } else {
            layer.show();
          }
        } else {
          layer.hide();
        }
      });
    },
    initLegend = function() {
      var legend = new Legend({
        map: map,
        layerInfos: [{
          layer: aggLayer,
          title: 'Legend'
        }],
        autoUpdate: false
      }, document.getElementById('legend'));
      legend.startup();
    },
    populateFieldList = function(featureType) {
      // Clear the field name select.
      while (fieldNameSelect.firstChild)
        fieldNameSelect.removeChild(fieldNameSelect.firstChild);

      var optionIdx = 0;
      array.forEach(FIELDS[featureType], function(group, i) {
        var optgroup = document.createElement('optgroup');
        optgroup.label = group.label;
        fieldNameSelect.appendChild(optgroup);
        array.forEach(group.fields, function(field, i) {
          var option = document.createElement('option');
          option.value = optionIdx;
          option.appendChild(document.createTextNode(field.label));
          optgroup.appendChild(option);
          if (field.isDefault) fieldNameSelect.selectedIndex = optionIdx;
          optionIdx += 1;
        });
      });
    },
    getSelectedField = function() {
      var featureType =
          featureTypeSelect.options[featureTypeSelect.selectedIndex].value,
        idx = fieldNameSelect.selectedIndex,
        fieldIdx = 0;
      for (var g = 0; g < FIELDS[featureType].length; g++) {
        for (var f = 0; f < FIELDS[featureType][g].fields.length; f++) {
          if (fieldIdx == idx) return FIELDS[featureType][g].fields[f];
          fieldIdx += 1;
        }
      }
    },
    requestLayerInfo = function(url) {
      return request({
        url: url,
        content: {
          f: 'json'
        },
        callbackParamName: 'callback'
      });
    },
    initFieldSelection = function(aggInfo) {
      // Set the minimum value for the renderer to 0.
      aggInfo.drawingInfo.renderer.minValue = 0;
      aggRenderer = new ClassBreaksRenderer(aggInfo.drawingInfo.renderer);

      // Set up the change handler for feature type.
      featureTypeSelect.onchange = function() {
        populateFieldList(this.options[this.selectedIndex].value);
      };

      // Set the initial values of the feature type and field name selects.
      featureTypeSelect.selectedIndex = 0;
      featureTypeSelect.onchange();

      // Set up the click handler for the update map button.
      updateButton.onclick = function() {
        var selectedField = getSelectedField();
        updateAggLayer(selectedField);
        updateIndLayers(selectedField);
      };
      updateButton.removeAttribute('disabled');
    },
    map = new Map('map', {
      center: [-88.2, 40.1],
      zoom: 11,
      basemap: 'gray-vector'
    }),
    aggLayer = new FeatureLayer(AGG_URL + '/0', {
      mode: FeatureLayer.MODE_SNAPSHOT,
      outFields: ['*'],
      opacity: 0.5
    }),
    aggRenderer,
    crLayer = makeIndLayer(0, SimpleMarkerSymbol.STYLE_CIRCLE, 10, false),
    cwLayer = makeIndLayer(1, SimpleMarkerSymbol.STYLE_SQUARE, 10, false),
    psLayer = makeIndLayer(2, SimpleMarkerSymbol.STYLE_DIAMOND, 10, false),
    swLayer = makeIndLayer(3, MARKER_TYPE_LINE, 3, true),
    fieldNameSelect = document.getElementById('fieldName'),
    featureTypeSelect = document.getElementById('featureType'),
    updateButton = document.getElementById('updateMap'),
    featureFields = {};

    aggLayer.on('load', function(e) {
      initLegend();
      aggLayer.setScaleRange(0, 10000);
    });
    map.addLayers([aggLayer, crLayer, cwLayer, psLayer, swLayer]);

    // Request layer information.
    requestLayerInfo(AGG_URL + '/0').then(function(res) {
      // TEMP
      var json = [];
      array.forEach(res.fields, function(field, i) {
        json.push({
          label: field.alias.replace('Sidewalk ', '').replace('Curb Ramp ', '').replace('Crosswalk ', '').replace('Pedestrian Signal ', '').replace(' Score', ''),
          aggField: field.name,
          indField: field.name.replace('Sidewalk', '').replace('CurbRamp', '').replace('Crosswalk', '').replace('PedestrianSignal', '')
        });
      });
      console.log(JSON.stringify(json));

      initFieldSelection(res);
    });
});
<|endoftext|>


<commit_before>import errors from './errors';

// Returns `true` if the given array is not empty and at least one element is not `undefined`.
// hasContent :: any[] -> boolean
const hasContent = array => array.reduce(
  (hasContent, value) => hasContent || value !== undefined,
  false
);

// Remove all array indices for `undefined` values (besides the last element).
// removeUndefined :: any[] -> any[]
const removeUndefined = array => {
  const result = [];
  array.forEach((value, index) => {
    if (value !== undefined || index === array.length - 1) {
      result[index] = value;
    }
  });
  return result;
};

// Removes all empty observations and also undefined values from observations.
// cleanResult :: {}<any[]> -> {}<any[]>
const cleanResult = observed => {
  return Object.keys(observed).reduce((cleanedObserved, name) => {
    const observation = observed[name];
    if (hasContent(observation)) {
      cleanedObserved[name] = removeUndefined(observation);
    }
    return cleanedObserved;
  }, {});
};

// Create on value handler function for an Observable with the given name. Add each value to the value event list for
// this Observable and add `undefined` to the value event lists of all other Observables.
// Also assure that the observed value is never undefined.
// addValue :: {}<any[]> -> string -> any -> void
const addValue = (observing, name) => value => {
  expect(value).not.toBe(undefined);
  Object.keys(observing).forEach(oname => {
    observing[oname].push(oname === name ? value : undefined);
  });
};

// Can be used to set trailing undefined value in observable test results.
global.undef = undefined;

// The name of the global error stream in the test results.
const errorsName = 'errors';

// Collects all events emitted by the given observables until all observables are ended. Calls the given callback
// with the result.
// observe :: {}<Observable<any>> -> ({}<any[]> -> void) -> void
global.observe = (observables, callback) => {
  // All value events for ended Observables.
  const observed = {}; // {}<any[]>
  // Collect all value events for all active Observables here.
  const observing = {}; // {}<any[]>
  observing[errorsName] = [];
  errors.onValue(addValue(observing, errorsName));
  // Iterate all observables
  Object.keys(observables).forEach(name => {
    // Get observable
    const observable = observables[name];
    if (observing[name] != null) {
      throw Error('Naming conflict');
    }
    // Initialize arrays to collect events of the current observable
    observing[name] = [];
    // Register listener to collect events
    observable
      .onValue(addValue(observing, name))
      .onEnd(() => {
        // Move key/value
        observed[name] = observing[name];
        delete observing[name];
        // If all observables are done
        if (Object.keys(observing).length === 1) {
          observed[errorsName] = observing[errorsName];
          callback(cleanResult(observed));
          callback = null; // Make sure callback is only called once
        } else {
          Object.keys(observing).forEach(oname => {
            observing[oname].push(undefined);
          });
        }
      });
  });
};
<commit_msg>Simplify<commit_after>import errors from './errors';

// Returns `true` if the given array is not empty and at least one element is not `undefined`.
// hasContent :: any[] -> boolean
const hasContent = array => array.reduce(
  (hasContent, value) => hasContent || value !== undefined,
  false
);

// Remove all array indices for `undefined` values (besides the last element).
// removeUndefined :: any[] -> any[]
const removeUndefined = array => {
  const result = [];
  array.forEach((value, index) => {
    if (value !== undefined || index === array.length - 1) {
      result[index] = value;
    }
  });
  return result;
};

// Removes all empty observations and also undefined values from observations.
// cleanResult :: {}<any[]> -> {}<any[]>
const cleanResult = observed => Object.keys(observed).reduce(
  (cleanedObserved, name) => {
    const observation = observed[name];
    if (hasContent(observation)) {
      cleanedObserved[name] = removeUndefined(observation);
    }
    return cleanedObserved;
  }, {}
);

// Create on value handler function for an Observable with the given name. Add each value to the value event list for
// this Observable and add `undefined` to the value event lists of all other Observables.
// Also assure that the observed value is never undefined.
// addValue :: {}<any[]> -> string -> any -> void
const addValue = (observing, name) => value => {
  expect(value).not.toBe(undefined);
  Object.keys(observing).forEach(oname => {
    observing[oname].push(oname === name ? value : undefined);
  });
};

// Can be used to set trailing undefined value in observable test results.
global.undef = undefined;

// The name of the global error stream in the test results.
const errorsName = 'errors';

// Collects all events emitted by the given observables until all observables are ended. Calls the given callback
// with the result.
// observe :: {}<Observable<any>> -> ({}<any[]> -> void) -> void
global.observe = (observables, callback) => {
  // All value events for ended Observables.
  const observed = {}; // {}<any[]>
  // Collect all value events for all active Observables here.
  const observing = {}; // {}<any[]>
  observing[errorsName] = [];
  errors.onValue(addValue(observing, errorsName));
  // Iterate all observables
  Object.keys(observables).forEach(name => {
    // Get observable
    const observable = observables[name];
    if (observing[name] != null) {
      throw Error('Naming conflict');
    }
    // Initialize arrays to collect events of the current observable
    observing[name] = [];
    // Register listener to collect events
    observable
      .onValue(addValue(observing, name))
      .onEnd(() => {
        // Move key/value
        observed[name] = observing[name];
        delete observing[name];
        // If all observables are done
        if (Object.keys(observing).length === 1) {
          observed[errorsName] = observing[errorsName];
          callback(cleanResult(observed));
          callback = null; // Make sure callback is only called once
        } else {
          Object.keys(observing).forEach(oname => {
            observing[oname].push(undefined);
          });
        }
      });
  });
};
<|endoftext|>


<commit_before>Ext.define('devilry.administrator.studentsmanager.AddDeliveriesMixin', {
    //requires: [
        //'devilry.student.FileUploadPanel'
        //'devilry.administrator.studentsmanager.LocateGroup'
    //],

    deliveryTypes: {
        TYPE_ELECTRONIC: 0,
        TYPE_NON_ELECTRONIC: 1,
        TYPE_ALIAS: 2
    },

    //onPreviouslyApproved: function() {
        //this.down('studentsmanager_studentsgrid').selModel.select(1);
        //if(this.noneSelected()) {
            //this.onNotSingleSelected();
            //return;
        //}
        //var groupRecord = this.getSelection()[0];
        //console.log(groupRecord);

        ////this.assignmentgroupPrevApprovedStore.proxy.extraParams.filters = Ext.JSON.encode([{
            ////field: 'parentnode__parentnode__parentnode',
            ////comp: 'exact',

        //Ext.widget('window', {
            //width: 800,
            //height: 700,
            //modal: true,
            //maximizable: true,
            //layout: 'fit',
            //title: 'Select previously approved group',
            //items: {
                //xtype: 'locategroup',
                //store: this.assignmentgroupPrevApprovedStore,
                //groupRecord: groupRecord
            //}
        //}).show();

        ////this.refreshStore();
    //},

    //onCreateDummyDelivery: function() {
        //if(!this.singleSelected()) {
            //this.onNotSingleSelected();
            //return;
        //}
        //var groupRecord = this.getSelection()[0];

        //var msg = Ext.create('Ext.XTemplate',
            //'
Are you sure you want to create a dummy delivery for ',
            //'    <tpl if="name">',
            //'        {name}: ',
            //'    </tpl>',
            //'    <tpl for="candidates__identifier">',
            //'        {.}<tpl if="xindex < xcount">, </tpl>',
            //'    </tpl>',
            //'?',
            //'<tpl if="number_of_deliveries > 0">',
            //'
WARNING: One usually only creates dummy deliveries for groups with no ',
            //'   deliveries, however this group has {number_of_deliveries} deliveries.
',
            //'</tpl>'
        //);
        //var me = this;
        //Ext.MessageBox.show({
            //title: 'Confirm that you want to create dummy delivery',
            //msg: msg.apply(groupRecord.data),
            //animateTarget: this.deletebutton,
            //buttons: Ext.Msg.YESNO,
            //icon: (groupRecord.data.number_of_deliveries>0? Ext.Msg.WARNING: Ext.Msg.QUESTION),
            //fn: function(btn) {
                //if(btn == 'yes') {
                    //me.createDummyDeliveryForSelected(groupRecord);
                //}
            //}
        //});
    //},

    /**
     * @private
     */
    //createDummyDelivery: function(groupRecord) {
        //var delivery = this.createDeliveryRecord(groupRecord, this.deliveryTypes.TYPE_ELECTRONIC);
        //delivery.save();
        //this.refreshStore();
    //},

    onPreviouslyPassed: function() {
        //this.down('studentsmanager_studentsgrid').selModel.select(1);
        if(this.noneSelected()) {
            this.onSelectNone();
            return;
        }
        this.prevPassedIntro();
    },

    /**
     * @private
     */
    prevPassedIntro: function() {
        Ext.widget('window', {
            width: 500,
            height: 400,
            layout: 'fit',
            modal: true,
            title: 'Mark as delivered in a previous period',
            items: {
                'xtype': 'panel',
                frame: false,
                border: false,
                html:
                    '
' +
                    '
Marking a group as delivered in a previoud period/semester, does the following:
' +
                    '
' +
                    '
Create a new empty delivery that is marked as imported from a previous semester. This is done automatically.
' +
                    '
Create a feedback for the new fake delivery using the grade editor configured for this assignment.
' +
                    '
' +
                    '
Click next to create the feedback. The feedback you select will be applied to each selected group.
' +
                    '
'
            },

            dockedItems: [{
                xtype: 'toolbar',
                dock: 'bottom',
                ui: 'footer',
                items: ['->', {
                    xtype: 'button',
                    iconCls: 'icon-next-32',
                    scale: 'large',
                    text: 'Next',
                    listeners: {
                        scope: this,
                        click: function(button) {
                            button.up('window').close();
                            this.prevPassedGiveFeedbackToSelected();
                        }
                    }
                }]
            }]
        }).show();
    },

    /**
     * @private
     */
    prevPassedGiveFeedbackToSelected: function() {
        var draftEditor = Ext.create('devilry.gradeeditors.EditManyDraftEditorWindow', {
            isAdministrator: this.isAdministrator,
            gradeeditor_config: this.gradeeditor_config_recordcontainer.record.data,
            registryitem: this.registryitem_recordcontainer.record.data,
            buttonText: 'Next',
            buttonIcon: 'icon-next-32',
            listeners: {
                scope: this,
                createNewDraft: this.prevPassedOnPublishFeedback
            }
        });
        draftEditor.show();
    },

    /**
     * @private
     */
    prevPassedOnPublishFeedback: function(feedbackdraftModelName, draftstring) {
        //this.down('studentsmanager_studentsgrid').selModel.selectAll();
        this.down('studentsmanager_studentsgrid').performActionOnSelected({
            scope: this,
            callback: this.createPreviouslyPassedDelivery,
            extraArgs: [feedbackdraftModelName, draftstring]
        });
    },

    /**
     * @private
     */
    createPreviouslyPassedDelivery: function(groupRecord, index, total, feedbackdraftModelName, draftstring) {
        var msg = Ext.String.format('Creating a delivery on group {0}/{1}', index, total);
        this.getEl().mask(msg);

        var delivery = this.createDeliveryRecord(groupRecord, this.deliveryTypes.TYPE_ALIAS);
        delivery.save({
            scope: this,
            success: function(record) {
                groupRecord.data.latest_delivery_id = record.data.id;
                this.giveFeedbackToSelected(groupRecord, index, total, feedbackdraftModelName, draftstring);
            }
        });
    },

    /**
     * @private
     */
    createDeliveryRecord: function(groupRecord, deliveryType) {
        return Ext.create('devilry.apps.administrator.simplified.SimplifiedDelivery', {
            successful: true,
            deadline: groupRecord.data.latest_deadline_id,
            delivery_type: deliveryType
            //alias_delivery
        });
    }
});
<commit_msg>apps.administrator...AddDeliveriesMixin: Use MultiResultWindow.<commit_after>Ext.define('devilry.administrator.studentsmanager.AddDeliveriesMixin', {
    //requires: [
        //'devilry.student.FileUploadPanel'
        //'devilry.administrator.studentsmanager.LocateGroup'
    //],

    deliveryTypes: {
        TYPE_ELECTRONIC: 0,
        TYPE_NON_ELECTRONIC: 1,
        TYPE_ALIAS: 2
    },

    //onPreviouslyApproved: function() {
        //this.down('studentsmanager_studentsgrid').selModel.select(1);
        //if(this.noneSelected()) {
            //this.onNotSingleSelected();
            //return;
        //}
        //var groupRecord = this.getSelection()[0];
        //console.log(groupRecord);

        ////this.assignmentgroupPrevApprovedStore.proxy.extraParams.filters = Ext.JSON.encode([{
            ////field: 'parentnode__parentnode__parentnode',
            ////comp: 'exact',

        //Ext.widget('window', {
            //width: 800,
            //height: 700,
            //modal: true,
            //maximizable: true,
            //layout: 'fit',
            //title: 'Select previously approved group',
            //items: {
                //xtype: 'locategroup',
                //store: this.assignmentgroupPrevApprovedStore,
                //groupRecord: groupRecord
            //}
        //}).show();

        ////this.refreshStore();
    //},

    //onCreateDummyDelivery: function() {
        //if(!this.singleSelected()) {
            //this.onNotSingleSelected();
            //return;
        //}
        //var groupRecord = this.getSelection()[0];

        //var msg = Ext.create('Ext.XTemplate',
            //'
Are you sure you want to create a dummy delivery for ',
            //'    <tpl if="name">',
            //'        {name}: ',
            //'    </tpl>',
            //'    <tpl for="candidates__identifier">',
            //'        {.}<tpl if="xindex < xcount">, </tpl>',
            //'    </tpl>',
            //'?',
            //'<tpl if="number_of_deliveries > 0">',
            //'
WARNING: One usually only creates dummy deliveries for groups with no ',
            //'   deliveries, however this group has {number_of_deliveries} deliveries.
',
            //'</tpl>'
        //);
        //var me = this;
        //Ext.MessageBox.show({
            //title: 'Confirm that you want to create dummy delivery',
            //msg: msg.apply(groupRecord.data),
            //animateTarget: this.deletebutton,
            //buttons: Ext.Msg.YESNO,
            //icon: (groupRecord.data.number_of_deliveries>0? Ext.Msg.WARNING: Ext.Msg.QUESTION),
            //fn: function(btn) {
                //if(btn == 'yes') {
                    //me.createDummyDeliveryForSelected(groupRecord);
                //}
            //}
        //});
    //},

    /**
     * @private
     */
    //createDummyDelivery: function(groupRecord) {
        //var delivery = this.createDeliveryRecord(groupRecord, this.deliveryTypes.TYPE_ELECTRONIC);
        //delivery.save();
        //this.refreshStore();
    //},

    onPreviouslyPassed: function() {
        //this.down('studentsmanager_studentsgrid').selModel.select(1);
        if(this.noneSelected()) {
            this.onSelectNone();
            return;
        }
        this.prevPassedIntro();
    },

    /**
     * @private
     */
    prevPassedIntro: function() {
        Ext.widget('window', {
            width: 500,
            height: 400,
            layout: 'fit',
            modal: true,
            title: 'Mark as delivered in a previous period',
            items: {
                'xtype': 'panel',
                frame: false,
                border: false,
                html:
                    '
' +
                    '
Marking a group as delivered in a previoud period/semester, does the following:
' +
                    '
' +
                    '
Create a new empty delivery that is marked as imported from a previous semester. This is done automatically.
' +
                    '
Create a feedback for the new fake delivery using the grade editor configured for this assignment.
' +
                    '
' +
                    '
Click next to create the feedback. The feedback you select will be applied to each selected group.
' +
                    '
'
            },

            dockedItems: [{
                xtype: 'toolbar',
                dock: 'bottom',
                ui: 'footer',
                items: ['->', {
                    xtype: 'button',
                    iconCls: 'icon-next-32',
                    scale: 'large',
                    text: 'Next',
                    listeners: {
                        scope: this,
                        click: function(button) {
                            button.up('window').close();
                            this.prevPassedGiveFeedbackToSelected();
                        }
                    }
                }]
            }]
        }).show();
    },

    /**
     * @private
     */
    prevPassedGiveFeedbackToSelected: function() {
        var draftEditor = Ext.create('devilry.gradeeditors.EditManyDraftEditorWindow', {
            isAdministrator: this.isAdministrator,
            gradeeditor_config: this.gradeeditor_config_recordcontainer.record.data,
            registryitem: this.registryitem_recordcontainer.record.data,
            buttonText: 'Next',
            buttonIcon: 'icon-next-32',
            listeners: {
                scope: this,
                createNewDraft: this.prevPassedOnPublishFeedback
            }
        });
        draftEditor.show();
    },

    /**
     * @private
     */
    prevPassedOnPublishFeedback: function(feedbackdraftModelName, draftstring) {
        //this.down('studentsmanager_studentsgrid').selModel.selectAll();
        this.progressWindow.start('Mark as delivered in a previous period');
        this._finishedSavingGroupCount = 0;
        this.down('studentsmanager_studentsgrid').performActionOnSelected({
            scope: this,
            callback: this.createPreviouslyPassedDelivery,
            extraArgs: [feedbackdraftModelName, draftstring]
        });
    },

    /**
     * @private
     */
    createPreviouslyPassedDelivery: function(groupRecord, index, total, feedbackdraftModelName, draftstring) {
        var msg = Ext.String.format('Creating a delivery on group {0}/{1}', index, total);
        this.getEl().mask(msg);

        var delivery = this.createDeliveryRecord(groupRecord, this.deliveryTypes.TYPE_ALIAS);
        delivery.save({
            scope: this,
            failure: function() {
                this.progressWindow.addErrorFromOperation(
                    groupRecord, 'Failed to create delivery', operation
                );
            },
            success: function(record) {
                groupRecord.data.latest_delivery_id = record.data.id;
                this.giveFeedbackToSelected(groupRecord, index, total, feedbackdraftModelName, draftstring);
            }
        });
    },

    /**
     * @private
     */
    createDeliveryRecord: function(groupRecord, deliveryType) {
        return Ext.create('devilry.apps.administrator.simplified.SimplifiedDelivery', {
            successful: true,
            deadline: groupRecord.data.latest_deadline_id,
            delivery_type: deliveryType
            //alias_delivery
        });
    }
});
<|endoftext|>


<commit_before>describe('Rivets.Binding', function() {
  var model, el, view, binding, opts;

  beforeEach(function() {
    adapter = rivets.adapters['.']

    el = document.createElement('div');
    el.setAttribute('data-text', 'obj.name');

    view = rivets.bind(el, {obj: {}});
    binding = view.bindings[0];
    model = binding.model;
  });

  it('gets assigned the proper binder routine matching the identifier', function() {
    expect(binding.binder.routine).toBe(rivets.binders.text);
  });

  describe('bind()', function() {
    it('subscribes to the model for changes via the adapter', function() {
      spyOn(adapter, 'subscribe');
      binding.bind();
      expect(adapter.subscribe).toHaveBeenCalledWith(model, 'name', binding.sync);
    });

    it("calls the binder's bind method if one exists", function() {
      expect(function(){
        binding.bind();
      }).not.toThrow(new Error());

      binding.binder.bind = function(){};
      spyOn(binding.binder, 'bind');
      binding.bind();
      expect(binding.binder.bind).toHaveBeenCalled();
    });

    describe('with preloadData set to true', function() {
      beforeEach(function() {
        rivets.config.preloadData = true;
      });

      it('sets the initial value via the adapter', function() {
        spyOn(binding, 'set');
        spyOn(adapter, 'read');
        binding.bind();
        expect(adapter.read).toHaveBeenCalledWith(model, 'name');
        expect(binding.set).toHaveBeenCalled();
      });
    });

    describe('with dependencies', function() {
      beforeEach(function() {
        binding.options.dependencies = ['.fname', '.lname'];
      });

      it('sets up observers on the dependant attributes', function() {
        spyOn(adapter, 'subscribe');
        binding.bind();
        expect(adapter.subscribe).toHaveBeenCalledWith(model, 'fname', binding.sync);
        expect(adapter.subscribe).toHaveBeenCalledWith(model, 'lname', binding.sync);
      });
    });
  });

  describe('unbind()', function() {
    it("calls the binder's unbind method if one exists", function() {
      expect(function(){
        binding.unbind();
      }).not.toThrow(new Error());

      binding.binder.unbind = function(){};
      spyOn(binding.binder, 'unbind');
      binding.unbind();
      expect(binding.binder.unbind).toHaveBeenCalled();
    });
  });

  describe('set()', function() {
    it('performs the binding routine with the supplied value', function() {
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'sweater');
    });

    it('applies any formatters to the value before performing the routine', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value; };
      binding.formatters.push('awesome');
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
    });

    it('calls methods with the object as context', function() {
      binding.model = {foo: 'bar'};
      spyOn(binding.binder, 'routine');
      binding.set(function() { return this.foo; });
      expect(binding.binder.routine).toHaveBeenCalledWith(el, binding.model.foo);
    });
  });

  describe('publish()', function() {
    it("should publish the value of a number input", function() {
      numberInput = document.createElement('input');
      numberInput.setAttribute('type', 'number');
      numberInput.setAttribute('data-value', 'obj.num');

      view = rivets.bind(numberInput, {obj: {num: 42}});
      binding = view.bindings[0];
      model = binding.model;

      numberInput.value = 42;

      spyOn(adapter, 'publish');
      binding.publish({target: numberInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'num', '42');
    });
  });

  describe('publishTwoWay()', function() {

    it('applies a two-way read formatter to function same as a single-way', function() {
      view.formatters.awesome = {
        read: function(value) { return 'awesome ' + value; },
      };
      binding.formatters.push('awesome');
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
    });

    it("should publish the value of a number input", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value; },
      };

      numberInput = document.createElement('input');
      numberInput.setAttribute('type', 'number');
      numberInput.setAttribute('data-value', 'obj.num | awesome');

      view = rivets.bind(numberInput, {obj: {num: 42}});
      binding = view.bindings[0];
      model = binding.model;

      numberInput.value = 42;

      spyOn(adapter, 'publish');
      binding.publish({target: numberInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'num', 'awesome 42');
    });

    it("should format a value in both directions", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value; },
        read: function(value) { return value + ' is awesome'; }
      };
      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(adapter, 'publish');
      valueInput.value = 'charles';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'awesome charles');

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome');
    });

    it("should not fail or format if the specified binding function doesn't exist", function() {
      rivets.formatters.awesome = { };
      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(adapter, 'publish');
      valueInput.value = 'charles';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'charles');

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred');
    });

    it("should apply read binders left to right, and write binders right to left", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally'; },
        read: function(value) { return value + ' totally'; }
      };
      rivets.formatters.awesome = {
        publish: function(value) { return value + ' is awesome'; },
        read: function(value) { return value + ' is awesome'; }
      };

      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome | totally');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');

      spyOn(adapter, 'publish');
      valueInput.value = 'fred';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is awesome');
    });

     it("binders in a chain should be skipped if they're not there", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally'; },
        read: function(value) { return value + ' totally'; }
      };
      rivets.formatters.radical = {
        publish: function(value) { return value + ' is radical'; },
      };
      rivets.formatters.awesome = function(value) { return value + ' is awesome'; };

      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome | radical | totally');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');

      spyOn(adapter, 'publish');
      valueInput.value = 'fred';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is radical');
    });

  });

  describe('formattedValue()', function() {
    it('applies the current formatters on the supplied value', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value; };
      binding.formatters.push('awesome');
      expect(binding.formattedValue('hat')).toBe('awesome hat');
    });

    it('uses formatters on the model', function() {
      model.modelAwesome = function(value) { return 'model awesome ' + value; };
      binding.formatters.push('modelAwesome');
      expect(binding.formattedValue('hat')).toBe('model awesome hat');
    });

    describe('with a multi-argument formatter string', function() {
      beforeEach(function() {
        view.formatters.awesome = function(value, prefix) {
          return prefix + ' awesome ' + value;
        };
        binding.formatters.push('awesome super');
      });

      it('applies the formatter with arguments', function() {
        expect(binding.formattedValue('jacket')).toBe('super awesome jacket');
      });
    });
  });
});
<commit_msg>Fix specs that where written agains the old data- attributes default.<commit_after>describe('Rivets.Binding', function() {
  var model, el, view, binding, opts;

  beforeEach(function() {
    rivets.config.prefix = 'data'
    adapter = rivets.adapters['.']

    el = document.createElement('div');
    el.setAttribute('data-text', 'obj.name');

    view = rivets.bind(el, {obj: {}});
    binding = view.bindings[0];
    model = binding.model;
  });

  it('gets assigned the proper binder routine matching the identifier', function() {
    expect(binding.binder.routine).toBe(rivets.binders.text);
  });

  describe('bind()', function() {
    it('subscribes to the model for changes via the adapter', function() {
      spyOn(adapter, 'subscribe');
      binding.bind();
      expect(adapter.subscribe).toHaveBeenCalledWith(model, 'name', binding.sync);
    });

    it("calls the binder's bind method if one exists", function() {
      expect(function(){
        binding.bind();
      }).not.toThrow(new Error());

      binding.binder.bind = function(){};
      spyOn(binding.binder, 'bind');
      binding.bind();
      expect(binding.binder.bind).toHaveBeenCalled();
    });

    describe('with preloadData set to true', function() {
      beforeEach(function() {
        rivets.config.preloadData = true;
      });

      it('sets the initial value via the adapter', function() {
        spyOn(binding, 'set');
        spyOn(adapter, 'read');
        binding.bind();
        expect(adapter.read).toHaveBeenCalledWith(model, 'name');
        expect(binding.set).toHaveBeenCalled();
      });
    });

    describe('with dependencies', function() {
      beforeEach(function() {
        binding.options.dependencies = ['.fname', '.lname'];
      });

      it('sets up observers on the dependant attributes', function() {
        spyOn(adapter, 'subscribe');
        binding.bind();
        expect(adapter.subscribe).toHaveBeenCalledWith(model, 'fname', binding.sync);
        expect(adapter.subscribe).toHaveBeenCalledWith(model, 'lname', binding.sync);
      });
    });
  });

  describe('unbind()', function() {
    it("calls the binder's unbind method if one exists", function() {
      expect(function(){
        binding.unbind();
      }).not.toThrow(new Error());

      binding.binder.unbind = function(){};
      spyOn(binding.binder, 'unbind');
      binding.unbind();
      expect(binding.binder.unbind).toHaveBeenCalled();
    });
  });

  describe('set()', function() {
    it('performs the binding routine with the supplied value', function() {
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'sweater');
    });

    it('applies any formatters to the value before performing the routine', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value; };
      binding.formatters.push('awesome');
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
    });

    it('calls methods with the object as context', function() {
      binding.model = {foo: 'bar'};
      spyOn(binding.binder, 'routine');
      binding.set(function() { return this.foo; });
      expect(binding.binder.routine).toHaveBeenCalledWith(el, binding.model.foo);
    });
  });

  describe('publish()', function() {
    it("should publish the value of a number input", function() {
      numberInput = document.createElement('input');
      numberInput.setAttribute('type', 'number');
      numberInput.setAttribute('data-value', 'obj.num');

      view = rivets.bind(numberInput, {obj: {num: 42}});
      binding = view.bindings[0];
      model = binding.model;

      numberInput.value = 42;

      spyOn(adapter, 'publish');
      binding.publish({target: numberInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'num', '42');
    });
  });

  describe('publishTwoWay()', function() {

    it('applies a two-way read formatter to function same as a single-way', function() {
      view.formatters.awesome = {
        read: function(value) { return 'awesome ' + value; },
      };
      binding.formatters.push('awesome');
      spyOn(binding.binder, 'routine');
      binding.set('sweater');
      expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
    });

    it("should publish the value of a number input", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value; },
      };

      numberInput = document.createElement('input');
      numberInput.setAttribute('type', 'number');
      numberInput.setAttribute('data-value', 'obj.num | awesome');

      view = rivets.bind(numberInput, {obj: {num: 42}});
      binding = view.bindings[0];
      model = binding.model;

      numberInput.value = 42;

      spyOn(adapter, 'publish');
      binding.publish({target: numberInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'num', 'awesome 42');
    });

    it("should format a value in both directions", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value; },
        read: function(value) { return value + ' is awesome'; }
      };
      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(adapter, 'publish');
      valueInput.value = 'charles';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'awesome charles');

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome');
    });

    it("should not fail or format if the specified binding function doesn't exist", function() {
      rivets.formatters.awesome = { };
      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(adapter, 'publish');
      valueInput.value = 'charles';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'charles');

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred');
    });

    it("should apply read binders left to right, and write binders right to left", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally'; },
        read: function(value) { return value + ' totally'; }
      };
      rivets.formatters.awesome = {
        publish: function(value) { return value + ' is awesome'; },
        read: function(value) { return value + ' is awesome'; }
      };

      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome | totally');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');

      spyOn(adapter, 'publish');
      valueInput.value = 'fred';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is awesome');
    });

     it("binders in a chain should be skipped if they're not there", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally'; },
        read: function(value) { return value + ' totally'; }
      };
      rivets.formatters.radical = {
        publish: function(value) { return value + ' is radical'; },
      };
      rivets.formatters.awesome = function(value) { return value + ' is awesome'; };

      valueInput = document.createElement('input');
      valueInput.setAttribute('type','text');
      valueInput.setAttribute('data-value', 'obj.name | awesome | radical | totally');

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
      binding = view.bindings[0];
      model = binding.model;

      spyOn(binding.binder, 'routine');
      binding.set('fred');
      expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');

      spyOn(adapter, 'publish');
      valueInput.value = 'fred';
      binding.publish({target: valueInput});
      expect(adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is radical');
    });

  });

  describe('formattedValue()', function() {
    it('applies the current formatters on the supplied value', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value; };
      binding.formatters.push('awesome');
      expect(binding.formattedValue('hat')).toBe('awesome hat');
    });

    it('uses formatters on the model', function() {
      model.modelAwesome = function(value) { return 'model awesome ' + value; };
      binding.formatters.push('modelAwesome');
      expect(binding.formattedValue('hat')).toBe('model awesome hat');
    });

    describe('with a multi-argument formatter string', function() {
      beforeEach(function() {
        view.formatters.awesome = function(value, prefix) {
          return prefix + ' awesome ' + value;
        };
        binding.formatters.push('awesome super');
      });

      it('applies the formatter with arguments', function() {
        expect(binding.formattedValue('jacket')).toBe('super awesome jacket');
      });
    });
  });
});
<|endoftext|>


<commit_before>describe('Rivets.Binding', function() {
  var model, el, view, binding, opts

  beforeEach(function() {
    rivets.config.prefix = 'data'
    adapter = rivets.adapters['.']

    el = document.createElement('div')
    el.setAttribute('data-text', 'obj.name')

    view = rivets.bind(el, {obj: {name: 'test'}})
    binding = view.bindings[0]
    model = binding.model
  })

  it('gets assigned the proper binder routine matching the identifier', function() {
    binding.binder.routine.should.equal(rivets.binders.text)
  })

  describe('bind()', function() {
    it('subscribes to the model for changes via the adapter', function() {
      sinon.spy(adapter, 'subscribe')
      binding.bind()
      adapter.subscribe.calledWith(model, 'name', binding.sync).should.be.true
    })

    it("calls the binder's bind method if one exists", function() {
      binding.bind.should.not.throw()

      binding.binder.bind = function(){}
      sinon.spy(binding.binder, 'bind')
      binding.bind()
      binding.binder.bind.called.should.be.true
    })

    describe('with preloadData set to true', function() {
      beforeEach(function() {
        rivets.config.preloadData = true
      })

      it('sets the initial value', function() {
        sinon.spy(binding, 'set')
        binding.bind()
        binding.set.calledWith('test').should.be.true
      })
    })

    describe('with dependencies', function() {
      beforeEach(function() {
        binding.options.dependencies = ['.fname', '.lname']
      })

      it('sets up observers on the dependant attributes', function() {
        binding.bind()
        adapter.subscribe.calledWith(model, 'fname', binding.sync).should.be.true
        adapter.subscribe.calledWith(model, 'lname', binding.sync).should.be.true
      })
    })
  })

  describe('unbind()', function() {
    it("calls the binder's unbind method if one exists", function() {
      binding.unbind.should.not.throw()

      binding.binder.unbind = function(){}
      sinon.spy(binding.binder, 'unbind')
      binding.unbind()
      binding.binder.unbind.called.should.be.true
    })
  })

  describe('set()', function() {
    it('performs the binding routine with the supplied value', function() {
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'sweater').should.be.true
    })

    it('applies any formatters to the value before performing the routine', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value }

      binding.formatters.push('awesome')
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'awesome sweater').should.be.true
    })

    it('calls methods with the object as context', function() {
      binding.model = {foo: 'bar'}
      sinon.spy(binding.binder, 'routine')
      binding.set(function() { return this.foo })
      binding.binder.routine.calledWith(el, binding.model.foo).should.be.true
    })
  })

  describe('publish()', function() {
    it("should publish the value of a number input", function() {
      numberInput = document.createElement('input')
      numberInput.setAttribute('type', 'number')
      numberInput.setAttribute('data-value', 'obj.num')

      view = rivets.bind(numberInput, {obj: {num: 42}})
      binding = view.bindings[0]
      model = binding.model

      numberInput.value = 42

      sinon.spy(adapter, 'publish')
      binding.publish({target: numberInput})
      adapter.publish.calledWith(model, 'num', '42').should.be.true
    })
  })

  describe('publishTwoWay()', function() {
    it('applies a two-way read formatter to function same as a single-way', function() {
      view.formatters.awesome = {
        read: function(value) { return 'awesome ' + value }
      }

      binding.formatters.push('awesome')
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'awesome sweater').should.be.true
    })

    it("should publish the value of a number input", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value }
      }

      numberInput = document.createElement('input')
      numberInput.setAttribute('type', 'number')
      numberInput.setAttribute('data-value', 'obj.num | awesome')

      view = rivets.bind(numberInput, {obj: {num: 42}})
      binding = view.bindings[0]
      model = binding.model

      numberInput.value = 42

      binding.publish({target: numberInput})
      adapter.publish.calledWith(model, 'num', 'awesome 42').should.be.true
    })

    it("should format a value in both directions", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value },
        read: function(value) { return value + ' is awesome' }
      }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      valueInput.value = 'charles'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'awesome charles').should.be.true

      sinon.spy(binding.binder, 'routine')
      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome').should.be.true
    })

    it("should not fail or format if the specified binding function doesn't exist", function() {
      rivets.formatters.awesome = { }
      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      valueInput.value = 'charles'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'charles').should.be.true

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred').should.be.true
    })

    it("should apply read binders left to right, and write binders right to left", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally' },
        read: function(value) { return value + ' totally' }
      }

      rivets.formatters.awesome = {
        publish: function(value) { return value + ' is awesome' },
        read: function(value) { return value + ' is awesome' }
      }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome | totally')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome totally').should.be.true

      valueInput.value = 'fred'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'fred totally is awesome').should.be.true
    })

    it("binders in a chain should be skipped if they're not there", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally' },
        read: function(value) { return value + ' totally' }
      }

      rivets.formatters.radical = {
        publish: function(value) { return value + ' is radical' },
      }

      rivets.formatters.awesome = function(value) { return value + ' is awesome' }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome | radical | totally')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome totally').should.be.true

      valueInput.value = 'fred'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'fred totally is radical').should.be.true
    })
  })

  describe('formattedValue()', function() {
    it('applies the current formatters on the supplied value', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value }
      binding.formatters.push('awesome')
      binding.formattedValue('hat').should.equal('awesome hat')
    })

    describe('with a multi-argument formatter string', function() {
      beforeEach(function() {
        view.formatters.awesome = function(value, prefix) {
          return prefix + ' awesome ' + value
        }

        binding.formatters.push('awesome super')
      })

      it('applies the formatter with arguments', function() {
        binding.formattedValue('jacket').should.equal('super awesome jacket')
      })
    })
  })
})
<commit_msg>Update binding specs. [#197]<commit_after>describe('Rivets.Binding', function() {
  var model, el, view, binding, opts

  beforeEach(function() {
    rivets.config.prefix = 'data'
    adapter = rivets.adapters['.']

    el = document.createElement('div')
    el.setAttribute('data-text', 'obj.name')

    view = rivets.bind(el, {obj: {name: 'test'}})
    binding = view.bindings[0]
    model = binding.model
  })

  it('gets assigned the proper binder routine matching the identifier', function() {
    binding.binder.routine.should.equal(rivets.binders.text)
  })

  describe('bind()', function() {
    it('subscribes to the model for changes via the adapter', function() {
      sinon.spy(adapter, 'subscribe')
      binding.bind()
      adapter.subscribe.calledWith(model, 'name', binding.sync).should.be.true
    })

    it("calls the binder's bind method if one exists", function() {
      binding.bind.should.not.throw()

      binding.binder.bind = function(){}
      sinon.spy(binding.binder, 'bind')
      binding.bind()
      binding.binder.bind.called.should.be.true
    })

    describe('with preloadData set to true', function() {
      beforeEach(function() {
        rivets.config.preloadData = true
      })

      it('sets the initial value', function() {
        sinon.spy(binding, 'set')
        binding.bind()
        binding.set.calledWith('test').should.be.true
      })
    })

    describe('with dependencies', function() {
      beforeEach(function() {
        binding.options.dependencies = ['.fname', '.lname']
      })

      it('sets up observers on the dependant attributes', function() {
        binding.bind()
        adapter.subscribe.calledWith(model, 'fname', binding.sync).should.be.true
        adapter.subscribe.calledWith(model, 'lname', binding.sync).should.be.true
      })
    })
  })

  describe('unbind()', function() {
    it("calls the binder's unbind method if one exists", function() {
      binding.unbind.should.not.throw()

      binding.binder.unbind = function(){}
      sinon.spy(binding.binder, 'unbind')
      binding.unbind()
      binding.binder.unbind.called.should.be.true
    })
  })

  describe('set()', function() {
    it('performs the binding routine with the supplied value', function() {
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'sweater').should.be.true
    })

    it('applies any formatters to the value before performing the routine', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value }

      binding.formatters.push('awesome')
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'awesome sweater').should.be.true
    })

    it('calls methods with the object as context', function() {
      binding.model = {foo: 'bar'}
      sinon.spy(binding.binder, 'routine')
      binding.set(function() { return this.foo })
      binding.binder.routine.calledWith(el, binding.model.foo).should.be.true
    })
  })

  describe('publish()', function() {
    it("should publish the value of a number input", function() {
      numberInput = document.createElement('input')
      numberInput.setAttribute('type', 'number')
      numberInput.setAttribute('data-value', 'obj.num')

      view = rivets.bind(numberInput, {obj: {num: 42}})
      binding = view.bindings[0]
      model = binding.model

      numberInput.value = 42

      sinon.spy(adapter, 'publish')
      binding.publish({target: numberInput})
      adapter.publish.calledWith(model, 'num', '42').should.be.true
    })
  })

  describe('publishTwoWay()', function() {
    it('applies a two-way read formatter to function same as a single-way', function() {
      view.formatters.awesome = {
        read: function(value) { return 'awesome ' + value }
      }

      binding.formatters.push('awesome')
      sinon.spy(binding.binder, 'routine')
      binding.set('sweater')
      binding.binder.routine.calledWith(el, 'awesome sweater').should.be.true
    })

    it("should publish the value of a number input", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value }
      }

      numberInput = document.createElement('input')
      numberInput.setAttribute('type', 'number')
      numberInput.setAttribute('data-value', 'obj.num | awesome')

      view = rivets.bind(numberInput, {obj: {num: 42}})
      binding = view.bindings[0]
      model = binding.model

      numberInput.value = 42

      binding.publish({target: numberInput})
      adapter.publish.calledWith(model, 'num', 'awesome 42').should.be.true
    })

    it("should format a value in both directions", function() {
      rivets.formatters.awesome = {
        publish: function(value) { return 'awesome ' + value },
        read: function(value) { return value + ' is awesome' }
      }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      valueInput.value = 'charles'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'awesome charles').should.be.true

      sinon.spy(binding.binder, 'routine')
      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome').should.be.true
    })

    it("should not fail or format if the specified binding function doesn't exist", function() {
      rivets.formatters.awesome = { }
      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      valueInput.value = 'charles'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'charles').should.be.true

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred').should.be.true
    })

    it("should apply read binders left to right, and write binders right to left", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally' },
        read: function(value) { return value + ' totally' }
      }

      rivets.formatters.awesome = {
        publish: function(value) { return value + ' is awesome' },
        read: function(value) { return value + ' is awesome' }
      }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome | totally')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome totally').should.be.true

      valueInput.value = 'fred'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'fred totally is awesome').should.be.true
    })

    it("binders in a chain should be skipped if they're not there", function() {
      rivets.formatters.totally = {
        publish: function(value) { return value + ' totally' },
        read: function(value) { return value + ' totally' }
      }

      rivets.formatters.radical = {
        publish: function(value) { return value + ' is radical' },
      }

      rivets.formatters.awesome = function(value) { return value + ' is awesome' }

      valueInput = document.createElement('input')
      valueInput.setAttribute('type','text')
      valueInput.setAttribute('data-value', 'obj.name | awesome | radical | totally')

      view = rivets.bind(valueInput, {obj: { name: 'nothing' }})
      binding = view.bindings[0]
      model = binding.model

      binding.set('fred')
      binding.binder.routine.calledWith(valueInput, 'fred is awesome totally').should.be.true

      valueInput.value = 'fred'
      binding.publish({target: valueInput})
      adapter.publish.calledWith(model, 'name', 'fred totally is radical').should.be.true
    })
  })

  describe('formattedValue()', function() {
    it('applies the current formatters on the supplied value', function() {
      view.formatters.awesome = function(value) { return 'awesome ' + value }
      binding.formatters.push('awesome')
      binding.formattedValue('hat').should.equal('awesome hat')
    })

    describe('with a multi-argument formatter string', function() {
      beforeEach(function() {
        view.formatters.awesome = function(value, prefix) {
          return prefix + ' awesome ' + value
        }

        binding.formatters.push("awesome 'super'")
      })

      it('applies the formatter with arguments', function() {
        binding.formattedValue('jacket').should.equal('super awesome jacket')
      })
    })
  })
})
<|endoftext|>


<commit_before>'use strict';

var translator = require('../lib/translator');

describe('Translator', function() {
  var itemData;
  var translateHook;

  beforeEach(function() {
    itemData      = { name: 'itemOne' };
    translateHook = jasmine.createSpy('translateHook').andReturn(itemData);
  });

  describe('ruleFor', function() {
    it('throws on non function', function() {
      expect(function() {
        translator.ruleFor('item', 'invalid hook');
      }).toThrow();

      expect(translator.item).not.toBeDefined();
    });

    it('assigns a rule', function() {
      translator.ruleFor('item', translateHook);
      expect(translator.item).toEqual(jasmine.any(Function));
    });
  });

  describe('to', function() {
    function ruleForItem(otherItem) {
      return {
        name: otherItem.key
      };
    }

    beforeEach(function() {
      translator.ruleFor('item', ruleForItem);
    });

    it('returns valid value for known name', function() {
      var converted = translator.to('item', { key: 'test'});
      expect(converted).toEqual({ name: 'test' });
    });

    describe('errors', function() {
      function validateData(data) {
        expect(function() {
          translator.translate('item', data);
        }).toThrow();
      }

      it('throws on invalid object data', function() {
        ['data', 123, [], null, undefined ].forEach(validateData);
      });

      it('throws on invalid hook return value', function() {
        translator.ruleFor('item', function() {});
        expect(function() {
          translator.translate('item', {});
        }).toThrow();
      });

      it('throws on nonexistent rule call', function() {
        expect(function() {
          translator.translate('nonexistent', {});
        }).toThrow();
      });
    });
  });
});
<commit_msg>Fix broken tests<commit_after>'use strict';

var translator = require('../lib/translator');

describe('Translator', function() {
  var itemData;
  var translateHook;

  beforeEach(function() {
    itemData      = { name: 'itemOne' };
    translateHook = jasmine.createSpy('translateHook').andReturn(itemData);
    delete translator.item;
  });

  describe('ruleFor', function() {
    it('throws on non function', function() {
      expect(function() {
        translator.ruleFor('item', 'invalid hook');
      }).toThrow();

      expect(translator.item).not.toBeDefined();
    });

    it('assigns a rule', function() {
      translator.ruleFor('item', translateHook);
      expect(translator.item).toEqual(jasmine.any(Function));
    });
  });

  describe('to', function() {
    function ruleForItem(otherItem) {
      return {
        name: otherItem.key
      };
    }

    beforeEach(function() {
      translator.ruleFor('item', ruleForItem);
    });

    it('returns valid value for known name', function() {
      var converted = translator.to('item', { key: 'test'});
      expect(converted).toEqual({ name: 'test' });
    });

    describe('errors', function() {
      function validateData(data) {
        expect(function() {
          translator.translate('item', data);
        }).toThrow();
      }

      it('throws on invalid object data', function() {
        ['data', 123, [], null, undefined ].forEach(validateData);
      });

      it('throws on invalid hook return value', function() {
        translator.ruleFor('item', function() {});
        expect(function() {
          translator.translate('item', {});
        }).toThrow();
      });

      it('throws on nonexistent rule call', function() {
        expect(function() {
          translator.translate('nonexistent', {});
        }).toThrow();
      });
    });
  });
});
<|endoftext|>


<commit_before>// See the file COPYRIGHT for copyright information.
//
// 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.

//
// HTML encoding
//

// **** BEGIN WEAKNESS ***

// It seems ridiculous that this isn't standard in JavaScript
// It is certainly ridiculous to involve the DOM, but on the other hand, the
// browser will implement this correctly, and any solution using .replace()
// will be buggy.  And this will be fast.  But still, this is weak.

var _domTextAreaForHaxxors = document.createElement("textarea")

function textAsHTML(text) {
    _domTextAreaForHaxxors.textContent = text;
    return _domTextAreaForHaxxors.innerHTML;
}

function htmlAsText(html) {
    _domTextAreaForHaxxors.innerHTML = html;
    return _domTextAreaForHaxxors.textContent;
}

// **** END WEAKNESS ***

//
// Generic string formatting
//

function padTwo(segment) {
  if (segment == undefined) {
    return "?";
  };

  segment = segment.toString();

  if (segment.length == 1) {
    return "0" + segment;
  };

  return "" + segment;
};

// FIXME: Try out momentjs: http://momentjs.com/docs/
function shortFormatDate(date) {
  return (
    padTwo(date.getMonth() + 1) + "/" +
    padTwo(date.getDate())      + "@" +
    padTwo(date.getHours())     + ":" +
    padTwo(date.getMinutes())
  );
};

//
// Incident data
//

function priorityNameFromNumber(priorityNumber) {
  // priorityNumber should be an int, 1-5.

  switch (priorityNumber) {
    case 1: return "⬆";
    case 2: return "⬆";
    case 3: return "•";
    case 4: return "⬇";
    case 5: return "⬇";
    default:
      console.log("Unknown incident priority number: " + priorityNumber);
      return undefined;
  };
};

function stateNameFromID(stateID) {
  // stateID should be a string key.

  switch (stateID) {
    case "new"       : return "New";
    case "on_hold"   : return "On Hold";
    case "dispatched": return "Dispatched";
    case "on_scene"  : return "On Scene";
    case "closed"    : return "Closed";
    default:
      console.log("Unknown incident state ID: " + stateID);
      return undefined;
  };
};

function stateSortKeyFromID(stateID) {
  // stateID should be a string key.

  switch (stateID) {
    case "new"       : return 1;
    case "on_hold"   : return 2;
    case "dispatched": return 3;
    case "on_scene"  : return 4;
    case "closed"    : return 5;
    default:
      console.log("Unknown incident state ID: " + stateID);
      return undefined;
  };
};

function concentricStreetFromID(streetID) {
  // streetID should be an int

  if (streetID == undefined) {
    return undefined;
  }

  var name = concentricStreetNameByNumber[streetID];
  if (name == undefined) {
    console.log("Unknown street ID: " + streetID);
    name = undefined;
  };
  return name;
};

function summarizeIncident(incident) {
  var summary = incident.summary;
  var reportEntries = incident.report_entries;

  if (summary == undefined) {
    if (reportEntries == undefined) {
      console.log("No summary provided.");
      return "";
    }
    else {
      // Get the first line of the first report entry.
      for (var i in reportEntries) {
        var lines = reportEntries[i].text.split("\n");

        for (var j in lines) {
          var line = lines[j];
          if (line == undefined || line == "") {
            continue;
          };
          summary = line;
          break;
        }

        if (summary != undefined) {
          break;
        }
      };

      return summary;
    };

    console.log("No summary provided and no report entry text.");
    return "";
  };

  return summary;
};

function shortDescribeLocation(location) {
  if (location == undefined) {
    return undefined;
  }

  var locationBits = [];

  if (location.name != undefined) {
    locationBits.push(location.name);
  };

  switch (location.type) {
    case undefined:
      // Fall through to "text" case
    case "text":
      break;
    case "garett":
      locationBits.push(" (");
      locationBits.push(padTwo(location.radial_hour));
      locationBits.push(":");
      locationBits.push(padTwo(location.radial_minute));
      locationBits.push("@");
      locationBits.push(concentricStreetFromID(location.concentric));
      locationBits.push(")");
      break;
    default:
      locationBits.push(
        "Unknown location type:" + location.type
      );
      break;
  };

  return locationBits.join("");
};

//
// DataTables rendering
//

function renderPriority(priorityNumber, type, incident) {
  switch (type) {
    case "display":
    case "filter":
      return textAsHTML(priorityNameFromNumber(priorityNumber));
    case "type":
    case "sort":
      return priorityNumber;
  };
  return undefined;
};

function renderDate(date, type, incident) {
  switch (type) {
    case "display":
    case "filter":
      return textAsHTML(shortFormatDate(date));
    case "type":
    case "sort":
      return date;
  };
  return undefined;
};

function renderState(state, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
      return textAsHTML(stateNameFromID(state));
    case "sort":
      return stateSortKeyFromID(state);
  };
  return undefined;
};

function renderLocation(data, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
    case "sort":
      return textAsHTML(shortDescribeLocation(data));
  };
  return undefined;
};

function renderSummary(data, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
    case "sort":
      return textAsHTML(summarizeIncident(incident));
  };
  return undefined;
};
<commit_msg>Be less obnoxious about semicolons.<commit_after>// See the file COPYRIGHT for copyright information.
//
// 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.

//
// HTML encoding
//

// **** BEGIN WEAKNESS ***

// It seems ridiculous that this isn't standard in JavaScript
// It is certainly ridiculous to involve the DOM, but on the other hand, the
// browser will implement this correctly, and any solution using .replace()
// will be buggy.  And this will be fast.  But still, this is weak.

var _domTextAreaForHaxxors = document.createElement("textarea")

function textAsHTML(text) {
    _domTextAreaForHaxxors.textContent = text;
    return _domTextAreaForHaxxors.innerHTML;
}

function htmlAsText(html) {
    _domTextAreaForHaxxors.innerHTML = html;
    return _domTextAreaForHaxxors.textContent;
}

// **** END WEAKNESS ***

//
// Generic string formatting
//

function padTwo(segment) {
  if (segment == undefined) {
    return "?";
  }

  segment = segment.toString();

  if (segment.length == 1) {
    return "0" + segment;
  }

  return "" + segment;
}

// FIXME: Try out momentjs: http://momentjs.com/docs/
function shortFormatDate(date) {
  return (
    padTwo(date.getMonth() + 1) + "/" +
    padTwo(date.getDate())      + "@" +
    padTwo(date.getHours())     + ":" +
    padTwo(date.getMinutes())
  );
}

//
// Incident data
//

function priorityNameFromNumber(priorityNumber) {
  // priorityNumber should be an int, 1-5.

  switch (priorityNumber) {
    case 1: return "⬆";
    case 2: return "⬆";
    case 3: return "•";
    case 4: return "⬇";
    case 5: return "⬇";
    default:
      console.log("Unknown incident priority number: " + priorityNumber);
      return undefined;
  }
}

function stateNameFromID(stateID) {
  // stateID should be a string key.

  switch (stateID) {
    case "new"       : return "New";
    case "on_hold"   : return "On Hold";
    case "dispatched": return "Dispatched";
    case "on_scene"  : return "On Scene";
    case "closed"    : return "Closed";
    default:
      console.log("Unknown incident state ID: " + stateID);
      return undefined;
  }
}

function stateSortKeyFromID(stateID) {
  // stateID should be a string key.

  switch (stateID) {
    case "new"       : return 1;
    case "on_hold"   : return 2;
    case "dispatched": return 3;
    case "on_scene"  : return 4;
    case "closed"    : return 5;
    default:
      console.log("Unknown incident state ID: " + stateID);
      return undefined;
  }
}

function concentricStreetFromID(streetID) {
  // streetID should be an int

  if (streetID == undefined) {
    return undefined;
  }

  var name = concentricStreetNameByNumber[streetID];
  if (name == undefined) {
    console.log("Unknown street ID: " + streetID);
    name = undefined;
  }
  return name;
}

function summarizeIncident(incident) {
  var summary = incident.summary;
  var reportEntries = incident.report_entries;

  if (summary == undefined) {
    if (reportEntries == undefined) {
      console.log("No summary provided.");
      return "";
    }
    else {
      // Get the first line of the first report entry.
      for (var i in reportEntries) {
        var lines = reportEntries[i].text.split("\n");

        for (var j in lines) {
          var line = lines[j];
          if (line == undefined || line == "") {
            continue;
          }
          summary = line;
          break;
        }

        if (summary != undefined) {
          break;
        }
      }

      return summary;
    }

    console.log("No summary provided and no report entry text.");
    return "";
  }

  return summary;
}

function shortDescribeLocation(location) {
  if (location == undefined) {
    return undefined;
  }

  var locationBits = [];

  if (location.name != undefined) {
    locationBits.push(location.name);
  }

  switch (location.type) {
    case undefined:
      // Fall through to "text" case
    case "text":
      break;
    case "garett":
      locationBits.push(" (");
      locationBits.push(padTwo(location.radial_hour));
      locationBits.push(":");
      locationBits.push(padTwo(location.radial_minute));
      locationBits.push("@");
      locationBits.push(concentricStreetFromID(location.concentric));
      locationBits.push(")");
      break;
    default:
      locationBits.push(
        "Unknown location type:" + location.type
      );
      break;
  }

  return locationBits.join("");
}

//
// DataTables rendering
//

function renderPriority(priorityNumber, type, incident) {
  switch (type) {
    case "display":
    case "filter":
      return textAsHTML(priorityNameFromNumber(priorityNumber));
    case "type":
    case "sort":
      return priorityNumber;
  }
  return undefined;
}

function renderDate(date, type, incident) {
  switch (type) {
    case "display":
    case "filter":
      return textAsHTML(shortFormatDate(date));
    case "type":
    case "sort":
      return date;
  }
  return undefined;
}

function renderState(state, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
      return textAsHTML(stateNameFromID(state));
    case "sort":
      return stateSortKeyFromID(state);
  }
  return undefined;
}

function renderLocation(data, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
    case "sort":
      return textAsHTML(shortDescribeLocation(data));
  }
  return undefined;
}

function renderSummary(data, type, incident) {
  switch (type) {
    case "display":
    case "filter":
    case "type":
    case "sort":
      return textAsHTML(summarizeIncident(incident));
  }
  return undefined;
}
<|endoftext|>


<commit_before>const GPIO = require('onoff').Gpio;
const config = require('../config/config.js');
const Device = require('../device.js');

//Create two lights corresponding to GPIO 23 & 24
const light1 = new Device('Light One', new GPIO(14, 'out'), config.ruleLightOne_On, config.ruleLightOne_Off);
const light2 = new Device('Light Two', new GPIO(15, 'out'), config.ruleLightTwo_On, config.ruleLightTwo_Off);

//Create pump corresponding to GPIO 25
const pump = new Device('Pump', new GPIO(18, 'out'), config.rulePump_On, config.rulePump_Off);

//Create wavemaker corresponding to 8
const wavemaker = new Device('Wave Maker', new GPIO(23, 'out'), config.ruleWaveMaker_On, config.ruleWaveMaker_Off);

//Create highpower light corresponding to 22
//const powerLight = new Device('Power Light', new GPIO(22, 'out'), config.rulePowerLight_On, config.rulePowerLight_Off);

//Create co2 valve corresponding to 10
//const co2Valve = new Device('CO2 Valve', new GPIO(10, 'out'), config.ruleCO2Valve_On, config.ruleCO2Valve_Off);

const aquarium = { };
aquarium[light1.name] = light1;
aquarium[light2.name] = light2;
aquarium[pump.name] = pump;
aquarium[wavemaker.name] = wavemaker;
//aquarium[powerLight.name] = powerLight;
//aquarium[co2Valve.name] = co2Valve;

module.exports = aquarium;
<commit_msg>Changing pins for SSR<commit_after>const GPIO = require('onoff').Gpio;
const config = require('../config/config.js');
const Device = require('../device.js');

const SSR_1 = 17;
const SSR_2 = 27;
const SSR_3 = 22;
const SSR_4 = 23;
//Create two lights corresponding to GPIO 23 & 24
const light1 = new Device('Light One', new GPIO(SSR_1, 'out'), config.ruleLightOne_On, config.ruleLightOne_Off);
const light2 = new Device('Light Two', new GPIO(SSR_2, 'out'), config.ruleLightTwo_On, config.ruleLightTwo_Off);

//Create pump corresponding to GPIO 25
const pump = new Device('Pump', new GPIO(SSR_3, 'out'), config.rulePump_On, config.rulePump_Off);

//Create wavemaker corresponding to 8
const wavemaker = new Device('Wave Maker', new GPIO(SSR_4, 'out'), config.ruleWaveMaker_On, config.ruleWaveMaker_Off);

//Create highpower light corresponding to 22
//const powerLight = new Device('Power Light', new GPIO(22, 'out'), config.rulePowerLight_On, config.rulePowerLight_Off);

//Create co2 valve corresponding to 10
//const co2Valve = new Device('CO2 Valve', new GPIO(10, 'out'), config.ruleCO2Valve_On, config.ruleCO2Valve_Off);

const aquarium = { };
aquarium[light1.name] = light1;
aquarium[light2.name] = light2;
aquarium[pump.name] = pump;
aquarium[wavemaker.name] = wavemaker;
//aquarium[powerLight.name] = powerLight;
//aquarium[co2Valve.name] = co2Valve;

module.exports = aquarium;
<|endoftext|>