<commit_before>/*
 * Leaflet.locationfilter - leaflet location filter plugin
 * Copyright (C) 2012, Tripbirds.com
 * http://tripbirds.com
 *
 * Licensed under the MIT License.
 *
 * Date: 2012-09-24
 * Version: 0.1
 */
L.LatLngBounds.prototype.modify = function(map, amount) {
    var sw = this.getSouthWest(),
        ne = this.getNorthEast(),
        swPoint = map.latLngToLayerPoint(sw),
        nePoint = map.latLngToLayerPoint(ne);

    sw = map.layerPointToLatLng(new L.Point(swPoint.x-amount, swPoint.y+amount));
    ne = map.layerPointToLatLng(new L.Point(nePoint.x+amount, nePoint.y-amount));

    return new L.LatLngBounds(sw, ne);
};

L.Control.Button = L.Class.extend({
    initialize: function(options) {
        L.Util.setOptions(this, options);
    },

    addTo: function(container) {
        container.addButton(this);
        return this;
    },

    onAdd: function (buttonContainer) {
        this._buttonContainer = buttonContainer;
        this._button = L.DomUtil.create('a', this.options.className, this._buttonContainer.getContainer());
        this._button.href = '#';
        this.setText(this.options.text);

        var that = this;
        this._onClick = function(event) {
            that.options.onClick.call(that, event);
        };

        L.DomEvent
            .on(this._button, 'click', L.DomEvent.stopPropagation)
            .on(this._button, 'mousedown', L.DomEvent.stopPropagation)
            .on(this._button, 'dblclick', L.DomEvent.stopPropagation)
            .on(this._button, 'click', L.DomEvent.preventDefault)
            .on(this._button, 'click', this._onClick, this);
    },

    remove: function() {
        L.DomEvent.off(this._button, "click", this._onClick);
        this._buttonContainer.getContainer().removeChild(this._button);
    },

    setText: function(text) {
        this._button.title = text;
        this._button.innerHTML = text;
    }
});

L.Control.ButtonContainer = L.Control.extend({
    options: {
        position: 'topleft'
    },

    getContainer: function() {
        if (!this._container) {
            this._container = L.DomUtil.create('div', this.options.className);
        }
        return this._container;
    },

    onAdd: function (map) {
        this._map = map;
        return this.getContainer();
    },

    addButton: function(button) {
        button.onAdd(this);
    },

    addClass: function(className) {
        L.DomUtil.addClass(this.getContainer(), className);
    },

    removeClass: function(className) {
        L.DomUtil.removeClass(this.getContainer(), className);
    }
});

L.LocationFilter = L.Class.extend({
    initialize: function(options) {
        L.Util.setOptions(this, options);
    },

    addTo: function(map) {
        map.addLayer(this);
        return this;
    },

    onAdd: function(map) {
        this._map = map;
        this._layer = new L.LayerGroup();
        this._initializeButtonContainer();

        if (this.options.enable) {
            this._enable();
        }
    },

    onRemove: function(map) {
        this._disable();
        this._buttonContainer.removeFrom(map);
    },

    /* Get the current filter bounds */
    getBounds: function() {
        return new L.LatLngBounds(this._sw, this._ne);
    },

    isEnabled: function() {
        return this._enabled;
    },

    /* Draw a rectangle */
    _drawRectangle: function(bounds, options) {
        options = options || {};
        var defaultOptions = {
            stroke: false,
            fill: true,
            fillColor: "black",
            fillOpacity: 0.3,
            clickable: false
        };
        options = L.Util.extend(defaultOptions, options);
        var rect = new L.Rectangle(bounds, options);
        rect.addTo(this._layer);
        return rect;
    },

    /* Draw a draggable marker */
    _drawImageMarker: function(point, options) {
        var marker = new L.Marker(point, {
            icon: new L.DivIcon({
                iconAnchor: options.anchor,
                iconSize: options.size,
                className: options.className
            }),
            draggable: true
        });
        marker.addTo(this._layer);
        return marker;
    },

    /* Draw a move marker. Sets up drag listener that updates the
       filter corners and redraws the filter when the move marker is
       moved */
    _drawMoveMarker: function(point) {
        var that = this;
        this._moveMarker = this._drawImageMarker(point, {
            "className": "location-filter move-marker",
            "anchor": [-10, -10],
            "size": [13,13]
        });
        this._moveMarker.on('drag', function(e) {
            var markerPos = that._moveMarker.getLatLng(),
                latDelta = markerPos.lat-that._nw.lat,
                lngDelta = markerPos.lng-that._nw.lng;
            that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta);
            that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta);
            that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta);
            that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta);
            that._draw();
        });
        this._setupDragendListener(this._moveMarker);
        return this._moveMarker;
    },

    /* Draw a resize marker */
    _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
        return this._drawImageMarker(point, {
            "className": "location-filter resize-marker",
            "anchor": [7, 6],
            "size": [13, 12]
        });
    },

    /* Track moving of the given resize marker and update the markers
       given in options.moveAlong to match the position of the moved
       marker. Update filter corners and redraw the filter */
    _setupResizeMarkerTracking: function(marker, options) {
        var that = this;
        marker.on('drag', function(e) {
            var curPosition = marker.getLatLng(),
                latMarker = options.moveAlong.lat,
                lngMarker = options.moveAlong.lng;
            // Move follower markers when this marker is moved
            latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng));
            lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng));
            // Sort marker positions in nw, ne, sw, se order
            var corners = [that._nwMarker.getLatLng(),
                           that._neMarker.getLatLng(),
                           that._swMarker.getLatLng(),
                           that._seMarker.getLatLng()];
            corners.sort(function(a, b) {
                if (a.lat != b.lat)
                    return b.lat-a.lat;
                else
                    return a.lng-b.lng;
            });
            // Update corner points and redraw everything except the resize markers
            that._nw = corners[0];
            that._ne = corners[1];
            that._sw = corners[2];
            that._se = corners[3];
            that._draw({repositionResizeMarkers: false});
        });
        this._setupDragendListener(marker);
    },

    /* Call the callback (given by name) if it was supplied in options */
    _callCallback: function(callbackName) {
        if (this.options[callbackName]) {
            this.options[callbackName](this.getBounds());
        }
    },

    /* Call the onChange callback whenever dragend is triggered on the
       given marker */
    _setupDragendListener: function(marker) {
        if (this.options.onChange) {
            var that = this;
            marker.on('dragend', function(e) {
                that._callCallback("onChange");
            });
        }
    },

    /* Create bounds for the mask rectangles and the location
       filter rectangle */
    _calculateBounds: function() {
        var mapBounds = this._map.getBounds(),
            outerBounds = new L.LatLngBounds(
                new L.LatLng(mapBounds.getSouthWest().lat-0.1,
                             mapBounds.getSouthWest().lng-0.1),
                new L.LatLng(mapBounds.getNorthEast().lat+0.1,
                             mapBounds.getNorthEast().lng+0.1)
            );

        // The south west and north east points of the mask */
        this._osw = outerBounds.getSouthWest();
        this._one = outerBounds.getNorthEast();

        // Bounds for the mask rectangles
        this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng), this._one),
        this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng), this._nw),
        this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng)),
        this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng));
    },

    /* Initializes rectangles and markers */
    _initialDraw: function() {
        if (this._initialDrawCalled) {
            return;
        }

        // Calculate filter bounds
        this._calculateBounds();

        // Create rectangles
        this._northRect = this._drawRectangle(this._northBounds);
        this._westRect = this._drawRectangle(this._westBounds);
        this._eastRect = this._drawRectangle(this._eastBounds);
        this._southRect = this._drawRectangle(this._southBounds);
        this._innerRect = this._drawRectangle(this.getBounds(), {
            fillColor: "transparent",
            stroke: true,
            color: "white",
            weight: 1,
            opacity: 0.9
        });

        // Create resize markers
        this._nwMarker = this._drawResizeMarker(this._nw);
        this._neMarker = this._drawResizeMarker(this._ne);
        this._swMarker = this._drawResizeMarker(this._sw);
        this._seMarker = this._drawResizeMarker(this._se);

        // Setup tracking of resize markers. Each marker has pair of
        // follower markers that must be moved whenever the marker is
        // moved. For example, whenever the north west resize marker
        // moves, the south west marker must move along on the x-axis
        // and the north east marker must move on the y axis
        this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
        this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
        this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
        this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});

        // Create move marker
        this._moveMarker = this._drawMoveMarker(this._nw);

        this._initialDrawCalled = true;
    },

    /* Reposition all rectangles and markers to the current filter bounds. */
    _draw: function(options) {
        options = L.Util.extend({repositionResizeMarkers: true}, options);

        // Calculate filter bounds
        this._calculateBounds();

        // Reposition rectangles
        this._northRect.setBounds(this._northBounds);
        this._westRect.setBounds(this._westBounds);
        this._eastRect.setBounds(this._eastBounds);
        this._southRect.setBounds(this._southBounds);
        this._innerRect.setBounds(this.getBounds());

        // Reposition resize markers
        if (options.repositionResizeMarkers) {
            this._nwMarker.setLatLng(this._nw);
            this._neMarker.setLatLng(this._ne);
            this._swMarker.setLatLng(this._sw);
            this._seMarker.setLatLng(this._se);
        }

        // Reposition the move marker
        this._moveMarker.setLatLng(this._nw);
    },

    /* Adjust the location filter to the current map bounds */
    _adjustToMap: function() {
        var mapBounds = this._map.getBounds();

        this._nw = mapBounds.getNorthWest();
        this._ne = mapBounds.getNorthEast();
        this._sw = mapBounds.getSouthWest();
        this._se = mapBounds.getSouthEast();

        this._draw();
        this._map.zoomOut();

        // Call change callback
        this._callCallback("onChange");
    },

    /* Enable the location filter */
    _enable: function() {
        // Initialize corners
        var bounds;
        if (this._sw && this._ne) {
            bounds = new L.LatLngBounds(this._sw, this._ne);
        } else if (this.options.bounds) {
            bounds = this.options.bounds;
        } else {
            bounds = this._map.getBounds();
        }
        this._map.invalidateSize();
        this._nw = bounds.getNorthWest();
        this._ne = bounds.getNorthEast();
        this._sw = bounds.getSouthWest();
        this._se = bounds.getSouthEast();

        // Update buttons
        this._buttonContainer.addClass("enabled");
        this._enableButton.setText("Remove selection");
        this._createAdjustButton();

        // Draw filter
        this._initialDraw();
        this._draw();

        // Set up map move event listener
        var that = this;
        this._moveHandler = function() {
            that._draw();
        };
        this._map.on("move", this._moveHandler);

        // Add the filter layer to the map
        this._layer.addTo(this._map);

        // Zoom out the map if necessary
        var mapBounds = this._map.getBounds();
        bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
        if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
            this._map.fitBounds(bounds);
        }

        this._enabled = true;

        // Call the enabled callback
        this._callCallback("onEnabled");
    },

    /* Disable the location filter */
    _disable: function() {
        // Update buttons
        this._buttonContainer.removeClass("enabled");
        this._enableButton.setText("Select area");
        this._adjustButton.remove();

        // Remove event listener
        this._map.off("move", this._moveHandler);

        // Remove rectangle layer from map
        this._map.removeLayer(this._layer);

        this._enabled = false;

        // Call the disabled callback
        this._callCallback("onDisabled");
    },

    /* Create a button that allows the user to adjust the location
       filter to the current zoom */
    _createAdjustButton: function() {
        var that = this;
        this._adjustButton = new L.Control.Button({
            className: "adjust-button",
            text: "Select area within current zoom",

            onClick: function(event) {
                that._adjustToMap();
                that._callCallback("onAdjustToZoomClick");
            }
        }).addTo(this._buttonContainer);
    },

    /* Create the location filter button container and the button that
       toggles the location filter */
    _initializeButtonContainer: function() {
        var that = this;
        this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
        this._enableButton = new L.Control.Button({
            className: "enable-button",
            text: "Select area",

            onClick: function(event) {
                if (!that._enabled) {
                    // Enable the location filter
                    that._enable();
                    that._callCallback("onEnableClick");
                } else {
                    // Disable the location filter
                    that._disable();
                    that._callCallback("onDisableClick");
                }
            }
        }).addTo(this._buttonContainer);
        this._buttonContainer.addTo(this._map);
    }
});<commit_msg>Make buttons optional and translatable<commit_after>/*
 * Leaflet.locationfilter - leaflet location filter plugin
 * Copyright (C) 2012, Tripbirds.com
 * http://tripbirds.com
 *
 * Licensed under the MIT License.
 *
 * Date: 2012-09-24
 * Version: 0.1
 */
L.LatLngBounds.prototype.modify = function(map, amount) {
    var sw = this.getSouthWest(),
        ne = this.getNorthEast(),
        swPoint = map.latLngToLayerPoint(sw),
        nePoint = map.latLngToLayerPoint(ne);

    sw = map.layerPointToLatLng(new L.Point(swPoint.x-amount, swPoint.y+amount));
    ne = map.layerPointToLatLng(new L.Point(nePoint.x+amount, nePoint.y-amount));

    return new L.LatLngBounds(sw, ne);
};

L.Control.Button = L.Class.extend({
    initialize: function(options) {
        L.Util.setOptions(this, options);
    },

    addTo: function(container) {
        container.addButton(this);
        return this;
    },

    onAdd: function (buttonContainer) {
        this._buttonContainer = buttonContainer;
        this._button = L.DomUtil.create('a', this.options.className, this._buttonContainer.getContainer());
        this._button.href = '#';
        this.setText(this.options.text);

        var that = this;
        this._onClick = function(event) {
            that.options.onClick.call(that, event);
        };

        L.DomEvent
            .on(this._button, 'click', L.DomEvent.stopPropagation)
            .on(this._button, 'mousedown', L.DomEvent.stopPropagation)
            .on(this._button, 'dblclick', L.DomEvent.stopPropagation)
            .on(this._button, 'click', L.DomEvent.preventDefault)
            .on(this._button, 'click', this._onClick, this);
    },

    remove: function() {
        L.DomEvent.off(this._button, "click", this._onClick);
        this._buttonContainer.getContainer().removeChild(this._button);
    },

    setText: function(text) {
        this._button.title = text;
        this._button.innerHTML = text;
    }
});

L.Control.ButtonContainer = L.Control.extend({
    options: {
        position: 'topleft'
    },

    getContainer: function() {
        if (!this._container) {
            this._container = L.DomUtil.create('div', this.options.className);
        }
        return this._container;
    },

    onAdd: function (map) {
        this._map = map;
        return this.getContainer();
    },

    addButton: function(button) {
        button.onAdd(this);
    },

    addClass: function(className) {
        L.DomUtil.addClass(this.getContainer(), className);
    },

    removeClass: function(className) {
        L.DomUtil.removeClass(this.getContainer(), className);
    }
});

L.LocationFilter = L.Class.extend({
    options: {
        enableButton: {
            enableText: "Select area",
            disableText: "Remove selection"
        },
        adjustButton: {
            text: "Select area within current zoom"
        }
    },

    initialize: function(options) {
        L.Util.setOptions(this, options);
    },

    addTo: function(map) {
        map.addLayer(this);
        return this;
    },

    onAdd: function(map) {
        this._map = map;
        this._layer = new L.LayerGroup();
        this._initializeButtonContainer();

        if (this.options.enable) {
            this._enable();
        }
    },

    onRemove: function(map) {
        this._disable();
        this._buttonContainer.removeFrom(map);
    },

    /* Get the current filter bounds */
    getBounds: function() {
        return new L.LatLngBounds(this._sw, this._ne);
    },

    isEnabled: function() {
        return this._enabled;
    },

    /* Draw a rectangle */
    _drawRectangle: function(bounds, options) {
        options = options || {};
        var defaultOptions = {
            stroke: false,
            fill: true,
            fillColor: "black",
            fillOpacity: 0.3,
            clickable: false
        };
        options = L.Util.extend(defaultOptions, options);
        var rect = new L.Rectangle(bounds, options);
        rect.addTo(this._layer);
        return rect;
    },

    /* Draw a draggable marker */
    _drawImageMarker: function(point, options) {
        var marker = new L.Marker(point, {
            icon: new L.DivIcon({
                iconAnchor: options.anchor,
                iconSize: options.size,
                className: options.className
            }),
            draggable: true
        });
        marker.addTo(this._layer);
        return marker;
    },

    /* Draw a move marker. Sets up drag listener that updates the
       filter corners and redraws the filter when the move marker is
       moved */
    _drawMoveMarker: function(point) {
        var that = this;
        this._moveMarker = this._drawImageMarker(point, {
            "className": "location-filter move-marker",
            "anchor": [-10, -10],
            "size": [13,13]
        });
        this._moveMarker.on('drag', function(e) {
            var markerPos = that._moveMarker.getLatLng(),
                latDelta = markerPos.lat-that._nw.lat,
                lngDelta = markerPos.lng-that._nw.lng;
            that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta);
            that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta);
            that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta);
            that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta);
            that._draw();
        });
        this._setupDragendListener(this._moveMarker);
        return this._moveMarker;
    },

    /* Draw a resize marker */
    _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
        return this._drawImageMarker(point, {
            "className": "location-filter resize-marker",
            "anchor": [7, 6],
            "size": [13, 12]
        });
    },

    /* Track moving of the given resize marker and update the markers
       given in options.moveAlong to match the position of the moved
       marker. Update filter corners and redraw the filter */
    _setupResizeMarkerTracking: function(marker, options) {
        var that = this;
        marker.on('drag', function(e) {
            var curPosition = marker.getLatLng(),
                latMarker = options.moveAlong.lat,
                lngMarker = options.moveAlong.lng;
            // Move follower markers when this marker is moved
            latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng));
            lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng));
            // Sort marker positions in nw, ne, sw, se order
            var corners = [that._nwMarker.getLatLng(),
                           that._neMarker.getLatLng(),
                           that._swMarker.getLatLng(),
                           that._seMarker.getLatLng()];
            corners.sort(function(a, b) {
                if (a.lat != b.lat)
                    return b.lat-a.lat;
                else
                    return a.lng-b.lng;
            });
            // Update corner points and redraw everything except the resize markers
            that._nw = corners[0];
            that._ne = corners[1];
            that._sw = corners[2];
            that._se = corners[3];
            that._draw({repositionResizeMarkers: false});
        });
        this._setupDragendListener(marker);
    },

    /* Call the callback (given by name) if it was supplied in options */
    _callCallback: function(callbackName) {
        if (this.options[callbackName]) {
            this.options[callbackName](this.getBounds());
        }
    },

    /* Call the onChange callback whenever dragend is triggered on the
       given marker */
    _setupDragendListener: function(marker) {
        if (this.options.onChange) {
            var that = this;
            marker.on('dragend', function(e) {
                that._callCallback("onChange");
            });
        }
    },

    /* Create bounds for the mask rectangles and the location
       filter rectangle */
    _calculateBounds: function() {
        var mapBounds = this._map.getBounds(),
            outerBounds = new L.LatLngBounds(
                new L.LatLng(mapBounds.getSouthWest().lat-0.1,
                             mapBounds.getSouthWest().lng-0.1),
                new L.LatLng(mapBounds.getNorthEast().lat+0.1,
                             mapBounds.getNorthEast().lng+0.1)
            );

        // The south west and north east points of the mask */
        this._osw = outerBounds.getSouthWest();
        this._one = outerBounds.getNorthEast();

        // Bounds for the mask rectangles
        this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng), this._one),
        this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng), this._nw),
        this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng)),
        this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng));
    },

    /* Initializes rectangles and markers */
    _initialDraw: function() {
        if (this._initialDrawCalled) {
            return;
        }

        // Calculate filter bounds
        this._calculateBounds();

        // Create rectangles
        this._northRect = this._drawRectangle(this._northBounds);
        this._westRect = this._drawRectangle(this._westBounds);
        this._eastRect = this._drawRectangle(this._eastBounds);
        this._southRect = this._drawRectangle(this._southBounds);
        this._innerRect = this._drawRectangle(this.getBounds(), {
            fillColor: "transparent",
            stroke: true,
            color: "white",
            weight: 1,
            opacity: 0.9
        });

        // Create resize markers
        this._nwMarker = this._drawResizeMarker(this._nw);
        this._neMarker = this._drawResizeMarker(this._ne);
        this._swMarker = this._drawResizeMarker(this._sw);
        this._seMarker = this._drawResizeMarker(this._se);

        // Setup tracking of resize markers. Each marker has pair of
        // follower markers that must be moved whenever the marker is
        // moved. For example, whenever the north west resize marker
        // moves, the south west marker must move along on the x-axis
        // and the north east marker must move on the y axis
        this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
        this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
        this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
        this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});

        // Create move marker
        this._moveMarker = this._drawMoveMarker(this._nw);

        this._initialDrawCalled = true;
    },

    /* Reposition all rectangles and markers to the current filter bounds. */
    _draw: function(options) {
        options = L.Util.extend({repositionResizeMarkers: true}, options);

        // Calculate filter bounds
        this._calculateBounds();

        // Reposition rectangles
        this._northRect.setBounds(this._northBounds);
        this._westRect.setBounds(this._westBounds);
        this._eastRect.setBounds(this._eastBounds);
        this._southRect.setBounds(this._southBounds);
        this._innerRect.setBounds(this.getBounds());

        // Reposition resize markers
        if (options.repositionResizeMarkers) {
            this._nwMarker.setLatLng(this._nw);
            this._neMarker.setLatLng(this._ne);
            this._swMarker.setLatLng(this._sw);
            this._seMarker.setLatLng(this._se);
        }

        // Reposition the move marker
        this._moveMarker.setLatLng(this._nw);
    },

    /* Adjust the location filter to the current map bounds */
    _adjustToMap: function() {
        var mapBounds = this._map.getBounds();

        this._nw = mapBounds.getNorthWest();
        this._ne = mapBounds.getNorthEast();
        this._sw = mapBounds.getSouthWest();
        this._se = mapBounds.getSouthEast();

        this._draw();
        this._map.zoomOut();

        // Call change callback
        this._callCallback("onChange");
    },

    /* Enable the location filter */
    _enable: function() {
        // Initialize corners
        var bounds;
        if (this._sw && this._ne) {
            bounds = new L.LatLngBounds(this._sw, this._ne);
        } else if (this.options.bounds) {
            bounds = this.options.bounds;
        } else {
            bounds = this._map.getBounds();
        }
        this._map.invalidateSize();
        this._nw = bounds.getNorthWest();
        this._ne = bounds.getNorthEast();
        this._sw = bounds.getSouthWest();
        this._se = bounds.getSouthEast();

        // Update buttons
        this._buttonContainer.addClass("enabled");

        if (this._enableButton) {
            this._enableButton.setText(this.options.enableButton.disableText);
        }

        if (this.options.adjustButton) {
            this._createAdjustButton();
        }

        // Draw filter
        this._initialDraw();
        this._draw();

        // Set up map move event listener
        var that = this;
        this._moveHandler = function() {
            that._draw();
        };
        this._map.on("move", this._moveHandler);

        // Add the filter layer to the map
        this._layer.addTo(this._map);

        // Zoom out the map if necessary
        var mapBounds = this._map.getBounds();
        bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
        if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
            this._map.fitBounds(bounds);
        }

        this._enabled = true;

        // Call the enabled callback
        this._callCallback("onEnabled");
    },

    /* Disable the location filter */
    _disable: function() {
        // Update buttons
        this._buttonContainer.removeClass("enabled");

        if (this._enableButton) {
            this._enableButton.setText(this.options.enableButton.enableText);
        }

        if (this._adjustButton) {
            this._adjustButton.remove();
        }

        // Remove event listener
        this._map.off("move", this._moveHandler);

        // Remove rectangle layer from map
        this._map.removeLayer(this._layer);

        this._enabled = false;

        // Call the disabled callback
        this._callCallback("onDisabled");
    },

    /* Create a button that allows the user to adjust the location
       filter to the current zoom */
    _createAdjustButton: function() {
        var that = this;
        this._adjustButton = new L.Control.Button({
            className: "adjust-button",
            text: this.options.adjustButton.text,

            onClick: function(event) {
                that._adjustToMap();
                that._callCallback("onAdjustToZoomClick");
            }
        }).addTo(this._buttonContainer);
    },

    /* Create the location filter button container and the button that
       toggles the location filter */
    _initializeButtonContainer: function() {
        var that = this;
        this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});

        if (this.options.enableButton) {
            this._enableButton = new L.Control.Button({
                className: "enable-button",
                text: this.options.enableButton.enableText,

                onClick: function(event) {
                    if (!that._enabled) {
                        // Enable the location filter
                        that._enable();
                        that._callCallback("onEnableClick");
                    } else {
                        // Disable the location filter
                        that._disable();
                        that._callCallback("onDisableClick");
                    }
                }
            }).addTo(this._buttonContainer);
        }

        this._buttonContainer.addTo(this._map);
    }
});<|endoftext|>


<commit_before>/**
 * @module log-timestamps
 *
 * Modifies console.log and friends to prepend a timestamp to log lines.
 */
/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*
 */

'use strict';

const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const UserProfile = require('./user-profile');
const format = require('util').format;

const formatter = winston.format.printf((info) => {
  return `${info.timestamp} ${info.level}: ${info.message}`;
});
const logger = winston.createLogger({
  level: 'debug',
  format: winston.format.combine(
    winston.format.colorize(),
    winston.format.padLevels(),
    winston.format.timestamp(),
    formatter
  ),
  transports: [
    new winston.transports.Console(),
    new DailyRotateFile({
      dirname: UserProfile.logDir,
      filename: 'run-app.log',
      zippedArchive: false,
      maxSize: '10m',
      maxFiles: 10,
    }),
  ],
  exitOnError: false,
});

function logPrefix() {
  const currTime = new Date();
  return `${currTime.getFullYear()}-${
    (`0${currTime.getMonth() + 1}`).slice(-2)}-${
    (`0${currTime.getDate()}`).slice(-2)} ${
    (`0${currTime.getHours()}`).slice(-2)}:${
    (`0${currTime.getMinutes()}`).slice(-2)}:${
    (`0${currTime.getSeconds()}`).slice(-2)}.${
    (`00${currTime.getMilliseconds()}`).slice(-3)} `;
}

if (!console.constructor.hooked) {
  console.constructor.hooked = true;

  // BufferedConsole is used (under jest) when running multiple tests
  // CustomConsole is used (under jest) when running a single test

  if (console.constructor.name === 'BufferedConsole') {
    // The code for the write function comes from the jest source code:
    // https://github.com/facebook/jest/blob/master/packages/jest-util/
    //    src/buffered_console.js

    const callsites = require('callsites');

    console.constructor.write = function write(buffer, type, message, level) {
      const call = callsites()[level != null ? level : 2];
      const origin = `${call.getFileName()}:${call.getLineNumber()}`;
      buffer.push({message: logPrefix() + message, origin, type});
      return buffer;
    };

    console.log = function log() {
      console.constructor.write(
        this._buffer, 'log', format.apply(null, arguments));
    };
    console.info = function info() {
      console.constructor.write(
        this._buffer, 'info', format.apply(null, arguments));
    };
    console.warn = function warn() {
      console.constructor.write(
        this._buffer, 'warn', format.apply(null, arguments));
    };
    console.error = function error() {
      console.constructor.write(
        this._buffer, 'error', format.apply(null, arguments));
    };
    // jest's BufferedConsole doesn't provide a debug, so we skip it as well.
  } else if (console.constructor.name === 'CustomConsole') {
    // See: https://github.com/facebook/jest/blob/master/packages/jest-util/
    //        src/Console.js
    //      for the implementation of _log

    console.log = function log() {
      console._log('log', logPrefix() + format.apply(null, arguments));
    };

    console.info = function info() {
      console._log('info', logPrefix() + format.apply(null, arguments));
    };

    console.warn = function warn() {
      console._log('warn', logPrefix() + format.apply(null, arguments));
    };

    console.error = function error() {
      console._log('error', logPrefix() + format.apply(null, arguments));
    };
    // jest's CustomConsole doesn't provide a debug, so we skip it as well.
  } else {
    // This path is for the normal non-jest output
    const FUNCS = ['info', 'debug', 'error', 'warn', 'verbose', 'silly'];

    for (const func of FUNCS) {
      console[func] = function() {
        logger[func](format.apply(null, arguments));
      };
    }

    console.log = console.info;
  }
}
<commit_msg>Colorize full log lines. (#1451)<commit_after>/**
 * @module log-timestamps
 *
 * Modifies console.log and friends to prepend a timestamp to log lines.
 */
/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*
 */

'use strict';

const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const UserProfile = require('./user-profile');
const format = require('util').format;

class CustomFormatter {
  transform(info) {
    const level = info.level.toUpperCase().padEnd(7, ' ');
    info.message = `${info.timestamp} ${level}: ${info.message}`;
    return info;
  }
}

const timestampFormat = winston.format.timestamp({
  format: 'YYYY-MM-DD HH:mm:ss.SSS',
});

const logger = winston.createLogger({
  level: 'debug',
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        timestampFormat,
        new CustomFormatter(),
        winston.format.colorize({
          all: true,
          colors: {
            debug: 'white',
            info: 'dim white',
            warn: 'yellow',
            error: 'red',
          },
        }),
        winston.format.printf((info) => info.message)
      ),
    }),
    new DailyRotateFile({
      dirname: UserProfile.logDir,
      filename: 'run-app.log',
      zippedArchive: false,
      maxSize: '10m',
      maxFiles: 10,
      format: winston.format.combine(
        timestampFormat,
        new CustomFormatter(),
        winston.format.printf((info) => info.message)
      ),
    }),
  ],
  exitOnError: false,
});

function logPrefix() {
  const currTime = new Date();
  return `${currTime.getFullYear()}-${
    (`0${currTime.getMonth() + 1}`).slice(-2)}-${
    (`0${currTime.getDate()}`).slice(-2)} ${
    (`0${currTime.getHours()}`).slice(-2)}:${
    (`0${currTime.getMinutes()}`).slice(-2)}:${
    (`0${currTime.getSeconds()}`).slice(-2)}.${
    (`00${currTime.getMilliseconds()}`).slice(-3)} `;
}

if (!console.constructor.hooked) {
  console.constructor.hooked = true;

  // BufferedConsole is used (under jest) when running multiple tests
  // CustomConsole is used (under jest) when running a single test

  if (console.constructor.name === 'BufferedConsole') {
    // The code for the write function comes from the jest source code:
    // https://github.com/facebook/jest/blob/master/packages/jest-util/
    //    src/buffered_console.js

    const callsites = require('callsites');

    console.constructor.write = function write(buffer, type, message, level) {
      const call = callsites()[level != null ? level : 2];
      const origin = `${call.getFileName()}:${call.getLineNumber()}`;
      buffer.push({message: logPrefix() + message, origin, type});
      return buffer;
    };

    console.log = function log() {
      console.constructor.write(
        this._buffer, 'log', format.apply(null, arguments));
    };
    console.info = function info() {
      console.constructor.write(
        this._buffer, 'info', format.apply(null, arguments));
    };
    console.warn = function warn() {
      console.constructor.write(
        this._buffer, 'warn', format.apply(null, arguments));
    };
    console.error = function error() {
      console.constructor.write(
        this._buffer, 'error', format.apply(null, arguments));
    };
    // jest's BufferedConsole doesn't provide a debug, so we skip it as well.
  } else if (console.constructor.name === 'CustomConsole') {
    // See: https://github.com/facebook/jest/blob/master/packages/jest-util/
    //        src/Console.js
    //      for the implementation of _log

    console.log = function log() {
      console._log('log', logPrefix() + format.apply(null, arguments));
    };

    console.info = function info() {
      console._log('info', logPrefix() + format.apply(null, arguments));
    };

    console.warn = function warn() {
      console._log('warn', logPrefix() + format.apply(null, arguments));
    };

    console.error = function error() {
      console._log('error', logPrefix() + format.apply(null, arguments));
    };
    // jest's CustomConsole doesn't provide a debug, so we skip it as well.
  } else {
    // This path is for the normal non-jest output
    const FUNCS = ['info', 'debug', 'error', 'warn', 'verbose', 'silly'];

    for (const func of FUNCS) {
      console[func] = function() {
        logger[func](format.apply(null, arguments));
      };
    }

    console.log = console.info;
  }
}
<|endoftext|>


<commit_before>var Connector = require('../../mixins/connector').Connector,
    Silence = require('../../mixins/silence').Silence,
    signalFactory = require('../../utils/signalFactory');

/**
 * Events are attached/detached with the returned function
 * options.events is a map that contains a mix of functions
 * or strings of methods names on the view instance. The strings
 * are used to retrieve bound methods from the view instance while
 * the functions are stored on options.events
 */
var eventHandler = function(handler){
    return function(){
        var events = this.options.events,
            element = this.element;

        if (element && events) {
            Object.each(events, function(val, key){
                var methods = Array.from(val),
                    len = methods.length,
                    i = 0, method;

                while(len--){
                    method = methods[i++];
                    this.element[handler](key, typeOf(method) == 'function' ? method : this.bound(method));
                }
            }, this);
        }

        return this;
    }
};

var View = new Class({
    Implements: [Connector, Events, Options, Silence],

    /**
     * Root element - contains all the elements that is to be created
     */
    // element: undefined,

    options: {
        // onReady: function(){},
        // onRender: function(){},
        // onInject: function(){},
        // onDispose: function(){},
        // onDestroy: function(){},
        element: undefined,
        events: {
            // 'click': 'nameOfMethod',
            // 'focus': function(){},
            // 'click:relay(a)': ['nameOfMethod', 'nameOfOtherMethod'],
            // 'click:relay(b)': [function(){}, 'nameOfOtherMethod'],
        }
    },

    initialize: function(options){
        this.setOptions(options);

        this.setup(options);

        this.signalReady();
    },

    setup: function(options){
        if (this.options.element) {
            this.setElement(this.options.element);
        }

        return this;
    },

    toElement: function(){
        return this.element;
    },

    setElement: function(element){
        if (element){
            this.element && this.destroy();

            element = this.element = document.id(element);
            if (element) {
                this.attachEvents();
            }
        }

        return this;
    },

    /**
     * Attaches the events found in options.events
     */
    attachEvents: eventHandler('addEvent'),

    /**
     * Detaches the events found in options.events
     */
    detachEvents: eventHandler('removeEvent'),

    /**
     * Override this function with another when extending View
     */
    create: function(){
        return this;
    },

    /**
     * Override this function with another when extending View
     * @param {Mixed} data While the argument is not used in the current render function,
     * it is there to help you understand that data should passed in to be used during
     * the render process.
     */
    render: function(data){
        this.signalRender.apply(this, arguments);
        return this;
    },

    /**
     * Inject the root element into another element or View instance. document.id will resolve the element from the View instance
     * @param  {Element | View} reference Element or View instance
     * @param  {String} where Defaults to Element.inject 'bottom' value
     * @return {Class} View instance
     */
    inject: function(reference, where){
        if (this.element){
            reference = document.id(reference);

            where = where || 'bottom';

            this.element.inject(reference, where);
            this.signalInject(reference, where);
        }

        return this;
    },

    /**
     * Dispose the element and signal dipose event
     */
    dispose: function(){
        if (this.element) {
            this.element.dispose();
            this.signalDispose();
        }

        return this;
    },

    /**
     * Detach the events, destroy the element from DOM and remove the reference to the element
     * before signaling destroy event
     */
    destroy: function(){
        var element = this.element;

        if (element){
            element && (this.detachEvents(), element.destroy(), this.element = undefined);

            this.signalDestroy();
        }
        return this;
    }
});

View.implement(
    signalFactory(
        ['ready', 'render', 'dispose', 'destroy', 'inject']
    )
);

exports.View = View;<commit_msg>Removing call to detach events. Elements.destroy already removes all the events before destroying.<commit_after>var Connector = require('../../mixins/connector').Connector,
    Silence = require('../../mixins/silence').Silence,
    signalFactory = require('../../utils/signalFactory');

/**
 * Events are attached/detached with the returned function
 * options.events is a map that contains a mix of functions
 * or strings of methods names on the view instance. The strings
 * are used to retrieve bound methods from the view instance while
 * the functions are stored on options.events
 */
var eventHandler = function(handler){
    return function(){
        var events = this.options.events,
            element = this.element;

        if (element && events) {
            Object.each(events, function(val, key){
                var methods = Array.from(val),
                    len = methods.length,
                    i = 0, method;

                while(len--){
                    method = methods[i++];
                    this.element[handler](key, typeOf(method) == 'function' ? method : this.bound(method));
                }
            }, this);
        }

        return this;
    }
};

var View = new Class({
    Implements: [Connector, Events, Options, Silence],

    /**
     * Root element - contains all the elements that is to be created
     */
    // element: undefined,

    options: {
        // onReady: function(){},
        // onRender: function(){},
        // onInject: function(){},
        // onDispose: function(){},
        // onDestroy: function(){},
        element: undefined,
        events: {
            // 'click': 'nameOfMethod',
            // 'focus': function(){},
            // 'click:relay(a)': ['nameOfMethod', 'nameOfOtherMethod'],
            // 'click:relay(b)': [function(){}, 'nameOfOtherMethod'],
        }
    },

    initialize: function(options){
        this.setOptions(options);

        this.setup(options);

        this.signalReady();
    },

    setup: function(options){
        if (this.options.element) {
            this.setElement(this.options.element);
        }

        return this;
    },

    toElement: function(){
        return this.element;
    },

    setElement: function(element){
        if (element){
            this.element && this.destroy();

            element = this.element = document.id(element);
            if (element) {
                this.attachEvents();
            }
        }

        return this;
    },

    /**
     * Attaches the events found in options.events
     */
    attachEvents: eventHandler('addEvent'),

    /**
     * Detaches the events found in options.events
     */
    detachEvents: eventHandler('removeEvent'),

    /**
     * Override this function with another when extending View
     */
    create: function(){
        return this;
    },

    /**
     * Override this function with another when extending View
     * @param {Mixed} data While the argument is not used in the current render function,
     * it is there to help you understand that data should passed in to be used during
     * the render process.
     */
    render: function(data){
        this.signalRender.apply(this, arguments);
        return this;
    },

    /**
     * Inject the root element into another element or View instance. document.id will resolve the element from the View instance
     * @param  {Element | View} reference Element or View instance
     * @param  {String} where Defaults to Element.inject 'bottom' value
     * @return {Class} View instance
     */
    inject: function(reference, where){
        if (this.element){
            reference = document.id(reference);

            where = where || 'bottom';

            this.element.inject(reference, where);
            this.signalInject(reference, where);
        }

        return this;
    },

    /**
     * Dispose the element and signal dipose event
     */
    dispose: function(){
        if (this.element) {
            this.element.dispose();
            this.signalDispose();
        }

        return this;
    },

    /**
     * Detach the events, destroy the element from DOM and remove the reference to the element
     * before signaling destroy event
     */
    destroy: function(){
        var element = this.element;

        if (element){
            element && (element.destroy(), this.element = undefined);

            this.signalDestroy();
        }
        return this;
    }
});

View.implement(
    signalFactory(
        ['ready', 'render', 'dispose', 'destroy', 'inject']
    )
);

exports.View = View;<|endoftext|>


<commit_before>'use strict';

var _ = require('underscore');
var randomlib = require('../../lib');
var async = require('async');
var path = require('path');

exports.randomlib = {
  generateRandomInt: function(test) {
    test.expect(1);

    var rand = new randomlib();

    var options = {
      min: 3,
      max: 10
    };
    rand.randomInt(options, function(err, result) {
      test.equal(true, result <= 10 && result >= 3, 'should get an integer less than 10 and more than 3');
      test.done();
    });
  },
  generateManyInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 2000
    };
    rand.randomInts(options, function(err, results) {
      test.equal(true, Array.isArray(results), 'should get an array of results');
      test.equal(true, results.length === 1000, 'should get 1000 results');
      test.done();
    });
  },
  generateManyBoundedInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 10
    };
    rand.randomInts(options, function(err, results) {
      test.equal(1000, results.length, 'should get 1000 results');

      var fail = false;
      for (var i = 0; i < results.length; i++) {
        if (results[i] < options.min || results[i] > options.max) {
          fail = true;
        }
      }
      test.equal(false, fail, 'should see all results within requested bounds');
      test.done();
    });
  },
  randomIntsDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInts(function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      test.done();
    });
  },
  randomIntDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInt(function(err, result) {
      test.equal(true, result <= 10 && result >= 0, 'should get an integer less than 10 and more than 0');
      test.done();
    });
  },
  randomFloat: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomFloat(function(err, result) {
      test.equal(true, result >= 0 && result <= 1, 'should be between 0 and 1');
      test.equal(true, result % 1 !== 0, 'should be a float');
      test.done();
    });
  },
  randomFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var fail = false;
    rand.randomFloats({num: 200}, function(err, results) {
      test.equal(200, results.length, 'should get 200 results');
      for(var i = 0; i < results.length; i++) {
        if (results[i] < 0 || results[i] > 1) {
          fail = true;
        }
        if (results[i] % 1 === 0) {
          fail = true;
        }
      }
      test.equal(false, fail, 'all results should be floats');
      test.done();
    });
  },
  multipleGenerateCalls: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomInts({num: 500}, function(err, results) {
      test.equal(500, results.length, 'should get 500 results');

      rand.randomInts({num: 1000}, function(err, results) {
        test.equal(1000, results.length, 'should get 1000 results');
        test.done();
      });
    });
  },
  randomUniqueInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 0, max: 9}, function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  tooManyRandomUniqueInts: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 3, max: 9}, function(err, results) {
      test.ok(err, 'should get an error message');
      test.done();
    });
  },
  randomUniqueFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueFloats({num: 10, min: 0, max: 9}, function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  promiseGenerateRandomInt: function(test) {
    test.expect(1);

    var rand = new randomlib();

    var options = {
      min: 3,
      max: 10
    };
    rand.randomInt(options).then(function (result) {
      test.equal(true, result <= 10 && result >= 3, 'should get an integer less than 10 and more than 3');
      test.done();
    });
  },
  promiseGenerateManyInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 2000
    };
    rand.randomInts(options).then(function (results) {
      test.equal(true, Array.isArray(results), 'should get an array of results');
      test.equal(true, results.length === 1000, 'should get 1000 results');
      test.done();
    });
  },
  promiseGenerateManyBoundedInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 10
    };
    rand.randomInts(options).then(function(results) {
      test.equal(1000, results.length, 'should get 1000 results');

      var fail = false;
      for (var i = 0; i < results.length; i++) {
        if (results[i] < options.min || results[i] > options.max) {
          fail = true;
        }
      }
      test.equal(false, fail, 'should see all results within requested bounds');
      test.done();
    });
  },
  promiseRandomIntsDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInts().then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      test.done();
    });
  },
  promiseRandomIntDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInt().then(function(result) {
      test.equal(true, result <= 10 && result >= 0, 'should get an integer less than 10 and more than 0');
      test.done();
    });
  },
  promiseRandomFloat: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomFloat().then(function(result) {
      test.equal(true, result >= 0 && result <= 1, 'should be between 0 and 1');
      test.equal(true, result % 1 !== 0, 'should be a float');
      test.done();
    });
  },
  promiseRandomFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var fail = false;
    rand.randomFloats({num: 200}).then(function(results) {
      test.equal(200, results.length, 'should get 200 results');
      for(var i = 0; i < results.length; i++) {
        if (results[i] < 0 || results[i] > 1) {
          fail = true;
        }
        if (results[i] % 1 === 0) {
          fail = true;
        }
      }
      test.equal(false, fail, 'all results should be floats');
      test.done();
    });
  },
  promiseMultipleGenerateCalls: function(test) {
    test.expect(2);

    var rand = new randomlib();

    async.series([function(cb) {
      rand.randomInts({num: 500}, function(err, results) {
        test.equal(500, results.length, 'should get 500 results');
        cb();
      });
    }, function(cb) {
      rand.randomInts({num: 1000}, function(err, results) {
        test.equal(1000, results.length, 'should get 1000 results');
        cb();
      });
    }], function(err) {
      test.done();
    });
  },
  promiseRandomUniqueInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 0, max: 9}).then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  promiseTooManyRandomUniqueInts: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 3, max: 9}).then(function(results) {
    }).catch(function (err) {
      test.ok(err, 'should get an error message');
      test.done();
    });
  },
  promiseRandomUniqueFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueFloats({num: 10, min: 0, max: 9}).then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  }
};
<commit_msg>adds parallel calls test<commit_after>'use strict';

var _ = require('underscore');
var randomlib = require('../../lib');
var async = require('async');
var path = require('path');

exports.randomlib = {
  generateRandomInt: function(test) {
    test.expect(1);

    var rand = new randomlib();

    var options = {
      min: 3,
      max: 10
    };
    rand.randomInt(options, function(err, result) {
      test.equal(true, result <= 10 && result >= 3, 'should get an integer less than 10 and more than 3');
      test.done();
    });
  },
  generateManyInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 2000
    };
    rand.randomInts(options, function(err, results) {
      test.equal(true, Array.isArray(results), 'should get an array of results');
      test.equal(true, results.length === 1000, 'should get 1000 results');
      test.done();
    });
  },
  generateManyBoundedInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 10
    };
    rand.randomInts(options, function(err, results) {
      test.equal(1000, results.length, 'should get 1000 results');

      var fail = false;
      for (var i = 0; i < results.length; i++) {
        if (results[i] < options.min || results[i] > options.max) {
          fail = true;
        }
      }
      test.equal(false, fail, 'should see all results within requested bounds');
      test.done();
    });
  },
  randomIntsDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInts(function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      test.done();
    });
  },
  randomIntDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInt(function(err, result) {
      test.equal(true, result <= 10 && result >= 0, 'should get an integer less than 10 and more than 0');
      test.done();
    });
  },
  randomFloat: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomFloat(function(err, result) {
      test.equal(true, result >= 0 && result <= 1, 'should be between 0 and 1');
      test.equal(true, result % 1 !== 0, 'should be a float');
      test.done();
    });
  },
  randomFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var fail = false;
    rand.randomFloats({num: 200}, function(err, results) {
      test.equal(200, results.length, 'should get 200 results');
      for(var i = 0; i < results.length; i++) {
        if (results[i] < 0 || results[i] > 1) {
          fail = true;
        }
        if (results[i] % 1 === 0) {
          fail = true;
        }
      }
      test.equal(false, fail, 'all results should be floats');
      test.done();
    });
  },
  multipleGenerateCalls: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomInts({num: 500}, function(err, results) {
      test.equal(500, results.length, 'should get 500 results');

      rand.randomInts({num: 1000}, function(err, results) {
        test.equal(1000, results.length, 'should get 1000 results');
        test.done();
      });
    });
  },
  parallelGenerateCalls: function(test) {
    var rand = new randomlib();
    var randomInts = rand.randomInts.bind(rand);

    var runTest = function(cb) {
      randomInts({num: 100}, function(err, results) {
        test.equal(100, results.length, 'should get 500 results');
        cb(null, results);
      });
    }

    var tests = [];
    for (var i = 0; i < 50; i++) {
      tests.push(runTest);
    }

    async.parallel(tests, function(err, result) {
      test.done();
    });
  },
  randomUniqueInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 0, max: 9}, function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  tooManyRandomUniqueInts: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 3, max: 9}, function(err, results) {
      test.ok(err, 'should get an error message');
      test.done();
    });
  },
  randomUniqueFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueFloats({num: 10, min: 0, max: 9}, function(err, results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  promiseGenerateRandomInt: function(test) {
    test.expect(1);

    var rand = new randomlib();

    var options = {
      min: 3,
      max: 10
    };
    rand.randomInt(options).then(function (result) {
      test.equal(true, result <= 10 && result >= 3, 'should get an integer less than 10 and more than 3');
      test.done();
    });
  },
  promiseGenerateManyInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 2000
    };
    rand.randomInts(options).then(function (results) {
      test.equal(true, Array.isArray(results), 'should get an array of results');
      test.equal(true, results.length === 1000, 'should get 1000 results');
      test.done();
    });
  },
  promiseGenerateManyBoundedInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var options = {
      num: 1000,
      min: 0,
      max: 10
    };
    rand.randomInts(options).then(function(results) {
      test.equal(1000, results.length, 'should get 1000 results');

      var fail = false;
      for (var i = 0; i < results.length; i++) {
        if (results[i] < options.min || results[i] > options.max) {
          fail = true;
        }
      }
      test.equal(false, fail, 'should see all results within requested bounds');
      test.done();
    });
  },
  promiseRandomIntsDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInts().then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      test.done();
    });
  },
  promiseRandomIntDefaults: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomInt().then(function(result) {
      test.equal(true, result <= 10 && result >= 0, 'should get an integer less than 10 and more than 0');
      test.done();
    });
  },
  promiseRandomFloat: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomFloat().then(function(result) {
      test.equal(true, result >= 0 && result <= 1, 'should be between 0 and 1');
      test.equal(true, result % 1 !== 0, 'should be a float');
      test.done();
    });
  },
  promiseRandomFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    var fail = false;
    rand.randomFloats({num: 200}).then(function(results) {
      test.equal(200, results.length, 'should get 200 results');
      for(var i = 0; i < results.length; i++) {
        if (results[i] < 0 || results[i] > 1) {
          fail = true;
        }
        if (results[i] % 1 === 0) {
          fail = true;
        }
      }
      test.equal(false, fail, 'all results should be floats');
      test.done();
    });
  },
  promiseMultipleGenerateCalls: function(test) {
    test.expect(2);

    var rand = new randomlib();

    async.series([function(cb) {
      rand.randomInts({num: 500}, function(err, results) {
        test.equal(500, results.length, 'should get 500 results');
        cb();
      });
    }, function(cb) {
      rand.randomInts({num: 1000}, function(err, results) {
        test.equal(1000, results.length, 'should get 1000 results');
        cb();
      });
    }], function(err) {
      test.done();
    });
  },
  promiseParallelGenerateCalls: function(test) {
    var rand = new randomlib();
    var randomInts = rand.randomInts.bind(rand);

    var runTest = function(cb) {
      randomInts({num: 100}).then(function(results) {
        test.equal(100, results.length, 'should get 500 results');
        cb(null, results);
      });
    }

    var tests = [];
    for (var i = 0; i < 50; i++) {
      tests.push(runTest);
    }

    async.parallel(tests, function(err, result) {
      test.done();
    });
  },
  promiseRandomUniqueInts: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 0, max: 9}).then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  },
  promiseTooManyRandomUniqueInts: function(test) {
    test.expect(1);

    var rand = new randomlib();

    rand.randomUniqueInts({num: 10, min: 3, max: 9}).then(function(results) {
    }).catch(function (err) {
      test.ok(err, 'should get an error message');
      test.done();
    });
  },
  promiseRandomUniqueFloats: function(test) {
    test.expect(2);

    var rand = new randomlib();

    rand.randomUniqueFloats({num: 10, min: 0, max: 9}).then(function(results) {
      test.equal(10, results.length, 'should get 10 results');
      var unique = _.uniq(results);
      test.equal(10, unique.length, 'should have 10 unique results');
      test.done();
    });
  }
};
<|endoftext|>


<commit_before>'use strict';

var
filepath = require('filepath'),
nodeUUID = require('node-uuid');

exports.fixturePath = filepath.create(__dirname).resolve('../fixtures');

exports.hasOwnProp = function (obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
};

exports.uuid = function () {
  return nodeUUID.v1();
};

exports.runOnce = (function () {
  var results = Object.create(null);
  return function (key, fn) {
    if (!results[key]) {
      results[key] = {
        val: fn.apply(null, arguments)
      };
    }
    return results[key].val;
  };
}());
<commit_msg>Remove node-uuid from tests.<commit_after>'use strict';

var
filepath = require('filepath');

exports.fixturePath = filepath.create(__dirname).resolve('../fixtures');

exports.hasOwnProp = function (obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
};

exports.runOnce = (function () {
  var results = Object.create(null);
  return function (key, fn) {
    if (!results[key]) {
      results[key] = {
        val: fn.apply(null, arguments)
      };
    }
    return results[key].val;
  };
}());
<|endoftext|>


<commit_before>/* eslint no-undef: off, no-unused-expressions: off */

/**
 * Created by Luke on 01/05/15.
 */

import { Client, endpoints } from '../../src/index';

/** @type{Client} */
let client;

describe('Client', () => {
  beforeEach(() => {
    client = new Client();
  });

  describe('options', () => {
    it('should use the sandbox', () => {
      expect(client.options.apiBase).to.equals('https://api.appnexus.com');
    });
  });

  describe('helpers check', () => {
    it('should tell me the token is expired', () => {
      client.options.token = { _ts: +new Date() - 60 * 60 * 1000, token: 'blaa' };

      expect(client.isExpired()).to.be.true;
    });

    it('should tell me the token is not expired', () => {
      client.options.token = { _ts: +new Date() - 10, token: 'blaa' };
      expect(client.isExpired()).to.be.false;
    });
  });

  describe('authorization', () => {
    if (typeof process.env.APPNEXUS_USERNAME === 'string'
    && typeof process.env.APPNEXUS_PASSWORD === 'string') {
      it('should preform an successful auth', () => {
        const authorize = client.authorize(
          process.env.APPNEXUS_USERNAME,
          process.env.APPNEXUS_PASSWORD,
        );
        expect(authorize).to.eventually.be.fullfilled;
      });
    }

    it('should throw an error when credentials are missing', () => {
      expect(client.authorize(null, null)).to.eventually.throw(Error, 'Authorization credentials are missing!');
    });
  });

  describe('request options overload', () => {
    beforeEach(() => {
      jest
        .spyOn(client, 'request')
        .mockImplementation((method, endpoint, ...args) => {
          const payload = {};

          const [data, headers = {}, dataType = 'formData'] = args;

          if (method === 'GET') {
            payload.json = true;
            payload.qs = data;
          } else if ('Content-Type' in headers
            && typeof headers['Content-Type'] === 'string'
            && headers['Content-Type'] !== 'application/json') {
            payload[dataType] = data;
          } else {
            payload.json = data;
          }

          return Object.assign(payload, {
            method,
            uri: `example.com/${endpoint}`,
            headers,
          });
        });
    });

    it('should contain json in payload ', async () => {
      const payload = await client.request('POST', null);

      expect(payload).to.contain.keys('json');
    });

    it('should contain formData in payload when changing content-type (POST, PUT, DELETE)', async () => {
      let payload = await client.post(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.put(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.delete(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.request('OPTIONS', null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');
    });

    it('should not contain formData in payload when changing content-type (GET)', async () => {
      const payload = await client.request('GET', null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.not.contain.keys('formData');
    });

    it('should contain custom payload key when changing content-type and overloading dataType', async () => {
      const payload = await client.request('POST', null, null, {
        'Content-Type': 'application/multipart',
      }, 'dataForm');

      expect(payload).to.contain.keys('dataForm');
    });

    afterAll(() => {
      jest.spyOn(client, 'request').mockRestore();
    });
  });

  describe('RateLimit', () => {
    beforeEach(() => {
      client = new Client(null, null, {
        auth: 1,
        write: 2,
        read: 3,
      });
    });

    it('should use the custom write limits', () => {
      expect(client.options.limits.auth).to.equals(1);
    });

    it('should use the custom read limits', () => {
      expect(client.options.limits.write).to.equals(2);
    });

    it('should use the custom write limits', () => {
      expect(client.options.limits.read).to.equals(3);
    });

    it('should limit auth requests', () => {
      const promise = client.rateLimiter('POST', endpoints.AUTHENTICATION_SERVICE);
      expect(promise).to.eventually.equal(0);
    });

    it('should limit get requests', () => {
      const promise = client.rateLimiter('POST', '');
      expect(promise).to.eventually.equal(1);
    });

    it('should limit post requests', () => {
      const promise = client.rateLimiter('GET', '');
      expect(promise).to.eventually.equal(2);
    });
  });
});
<commit_msg>Updated spec file to reflect non-breaking changes to existing api<commit_after>/* eslint no-undef: off, no-unused-expressions: off */

/**
 * Created by Luke on 01/05/15.
 */

import { Client, endpoints } from '../../src/index';

/** @type{Client} */
let client;

describe('Client', () => {
  beforeEach(() => {
    client = new Client();
  });

  describe('options', () => {
    it('should use the sandbox', () => {
      expect(client.options.apiBase).to.equals('https://api.appnexus.com');
    });
  });

  describe('helpers check', () => {
    it('should tell me the token is expired', () => {
      client.options.token = { _ts: +new Date() - 60 * 60 * 1000, token: 'blaa' };

      expect(client.isExpired()).to.be.true;
    });

    it('should tell me the token is not expired', () => {
      client.options.token = { _ts: +new Date() - 10, token: 'blaa' };
      expect(client.isExpired()).to.be.false;
    });
  });

  describe('authorization', () => {
    if (typeof process.env.APPNEXUS_USERNAME === 'string'
    && typeof process.env.APPNEXUS_PASSWORD === 'string') {
      it('should preform an successful auth', () => {
        const authorize = client.authorize(
          process.env.APPNEXUS_USERNAME,
          process.env.APPNEXUS_PASSWORD,
        );
        expect(authorize).to.eventually.be.fullfilled;
      });
    }

    it('should throw an error when credentials are missing', () => {
      expect(client.authorize(null, null)).to.eventually.throw(Error, 'Authorization credentials are missing!');
    });
  });

  describe('request options overload', () => {
    beforeEach(() => {
      jest
        .spyOn(client, 'request')
        .mockImplementation((method, endpoint, ...args) => {
          const payload = {};

          const [data, headers = {}, dataType = 'formData'] = args;

          if (method === 'GET') {
            payload.json = true;
            payload.qs = data;
          } else if ('Content-Type' in headers
            && typeof headers['Content-Type'] === 'string'
            && headers['Content-Type'] !== 'application/json') {
            payload[dataType] = data;
          } else {
            payload.json = data;
          }

          return Object.assign(payload, {
            method,
            uri: `example.com/${endpoint}`,
            headers,
          });
        });
    });

    it('should contain json in payload ', async () => {
      const payload = await client.request('POST', null);

      expect(payload).to.contain.keys('json');
    });

    it('should contain formData in payload when changing content-type (POST, PUT, DELETE)', async () => {
      let payload = await client.post(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.put(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.delete(null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');

      payload = await client.request('OPTIONS', null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.contain.keys('formData');
    });

    it('should not contain formData in payload when changing content-type (GET)', async () => {
      let payload = await client.request('GET', null, null, {
        'Content-Type': 'application/multipart',
      });

      expect(payload).to.not.contain.keys('formData');

      payload = await client.get(null, { q: 'apple' });

      expect(payload.qs.q).to.equals('apple');
    });

    it('should contain custom payload key when changing content-type and overloading dataType', async () => {
      const payload = await client.request('POST', null, null, {
        'Content-Type': 'application/multipart',
      }, 'dataForm');

      expect(payload).to.contain.keys('dataForm');
    });

    afterAll(() => {
      jest.spyOn(client, 'request').mockRestore();
    });
  });

  describe('RateLimit', () => {
    beforeEach(() => {
      client = new Client(null, null, {
        auth: 1,
        write: 2,
        read: 3,
      });
    });

    it('should use the custom write limits', () => {
      expect(client.options.limits.auth).to.equals(1);
    });

    it('should use the custom read limits', () => {
      expect(client.options.limits.write).to.equals(2);
    });

    it('should use the custom write limits', () => {
      expect(client.options.limits.read).to.equals(3);
    });

    it('should limit auth requests', () => {
      const promise = client.rateLimiter('POST', endpoints.AUTHENTICATION_SERVICE);
      expect(promise).to.eventually.equal(0);
    });

    it('should limit get requests', () => {
      const promise = client.rateLimiter('POST', '');
      expect(promise).to.eventually.equal(1);
    });

    it('should limit post requests', () => {
      const promise = client.rateLimiter('GET', '');
      expect(promise).to.eventually.equal(2);
    });
  });
});
<|endoftext|>


<commit_before>const {before} = require('mocha')

before(mockDOM)
before(mockCustomElements)
before(mockConnectedCallback)
// before(setChaiAssertions)

function mockDOM() {
  const {JSDOM: Dom} = require('jsdom')
  const dom = new Dom('<!doctype html>')
  global.document = dom.window.document
  global.window = document.defaultView
  window.Object = Object
  window.Math = Math
  global.NodeList = window.NodeList
  global.HTMLElement = window.HTMLElement
  global.environment = 'node'
}

function mockCustomElements() {
  require('document-register-element/pony')(window)
}

function mockConnectedCallback() {
  // jsdom dont call connectedCallback on appendChild, native customElements call connectedCallback
  const {Element} = window
  const appendChild = Element.prototype.appendChild
  Element.prototype.appendChild = function(element){
    appendChild.apply(this, arguments)
    if (element && element.connectedCallback) {
      element.connectedCallback()
    }
  }
}

// function setChaiAssertions() {
//   require('chai')
//     .use(require('chai-dom'))
// }
<commit_msg>delete trash comments from before unit tests<commit_after>const {before} = require('mocha')

before(mockDOM)
before(mockCustomElements)
before(mockConnectedCallback)

function mockDOM() {
  const {JSDOM: Dom} = require('jsdom')
  const dom = new Dom('<!doctype html>')
  global.document = dom.window.document
  global.window = document.defaultView
  window.Object = Object
  window.Math = Math
  global.NodeList = window.NodeList
  global.HTMLElement = window.HTMLElement
  global.environment = 'node'
}

function mockCustomElements() {
  require('document-register-element/pony')(window)
}

function mockConnectedCallback() {
  // jsdom dont call connectedCallback on appendChild, native customElements call connectedCallback
  const {Element} = window
  const appendChild = Element.prototype.appendChild
  Element.prototype.appendChild = function(element){
    appendChild.apply(this, arguments)
    if (element && element.connectedCallback) {
      element.connectedCallback()
    }
  }
}
<|endoftext|>


<commit_before>/**
 * .reel Unit Tests
 */
(function($){

  module('Sprite', { teardown: function teardown(){
    $('.jquery-reel').trigger('teardown');
  }});

  asyncTest( 'Multi-row: Frame shifting (4 rows)', function(){
		expect(12);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        frame: 2,
        footage: 3,
        rows: 4,
        row: 1
      }),
      /*
      Sprite layout:

      A1 A2 A3                     1  2  3
      A4 A5 A6                     4  5  6
      B1 B2 B3                     7  8  9
      B4 B5 B6  in real frames »  10 11 12
      C1 C2 C3                    13 14 15
      C4 C5 C6                    16 17 18
      D1 D2 D3                    19 20 21
      D4 D5 D6                    22 23 24

      (letter ~ row; number ~ frame)
      */
      entries= {
        '1': [         0,           2,      '-276px 0px' ],
        '2': [    0.3333,           8,      '-276px -252px' ],
        '3': [    0.6667,          14,      '-276px -504px' ],
        '4': [         1,          20,      '-276px -756px' ],
      }
    $.each(entries, function(ix,it){
      $reel.trigger('rowChange', Number(ix));
      equal( $reel.data('row'), it[0], 'Row '+ix+': Interpolated row');
      equal( $reel.data('frame'), it[1], 'Row '+ix+': Shifted frame');
      equal( $reel.css('backgroundPosition'), it[2], 'Row '+ix+': Sprite CSS background position');
    });
    start();
  });

  asyncTest( 'Multi-row: Uneven rows frame shifting (3 rows)', function(){
		expect(9);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        footage: 4,
        frame: 5,
        rows: 3,
        row: 3
      }),
      /*
      Sprite layout:

      A1 A2 A3 A4                    1  2  3  4
      A5 A6 B1 B2                    5  6  7  8
      B3 B4 B5 B6  in real frames »  9 10 11 12
      C1 C2 C3 C4                   13 14 15 16
      C5 C6                         17 18

      (letter ~ row; number ~ frame)
      */
      entries= {
        '3': [         1,          17,      '0px -504px' ],
        '2': [       0.5,          11,      '-552px -252px' ],
        '1': [         0,           5,      '0px -126px' ],
      }
    $.each(entries, function(ix,it){
      $reel.trigger('rowChange', Number(ix));
      equal( $reel.data('row'), it[0], 'Row '+ix+': Interpolated row');
      equal( $reel.data('frame'), it[1], 'Row '+ix+': Shifted frame');
      equal( $reel.css('backgroundPosition'), it[2], 'Row '+ix+': Sprite CSS background position');
    });
    start();
  });

  asyncTest( 'Multi-row: Immune to `backwards` value', function(){
		expect(6);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        footage: 4,
        frame: 5,
        rows: 3,
        row: 3
      })
    equal( $reel.data('row'), 1);
    equal( $reel.data('frame'), 17);
    equal( $reel.data('backwards'), false);

    $reel.data('backwards', true);
    equal( $reel.data('row'), 1);
    equal( $reel.data('frame'), 17);
    equal( $reel.data('backwards'), true);
    start();
  });

})(jQuery);<commit_msg>Removing trailing commas in objects for IE to not complain<commit_after>/**
 * .reel Unit Tests
 */
(function($){

  module('Sprite', { teardown: function teardown(){
    $('.jquery-reel').trigger('teardown');
  }});

  asyncTest( 'Multi-row: Frame shifting (4 rows)', function(){
		expect(12);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        frame: 2,
        footage: 3,
        rows: 4,
        row: 1
      }),
      /*
      Sprite layout:

      A1 A2 A3                     1  2  3
      A4 A5 A6                     4  5  6
      B1 B2 B3                     7  8  9
      B4 B5 B6  in real frames »  10 11 12
      C1 C2 C3                    13 14 15
      C4 C5 C6                    16 17 18
      D1 D2 D3                    19 20 21
      D4 D5 D6                    22 23 24

      (letter ~ row; number ~ frame)
      */
      entries= {
        '1': [         0,           2,      '-276px 0px' ],
        '2': [    0.3333,           8,      '-276px -252px' ],
        '3': [    0.6667,          14,      '-276px -504px' ],
        '4': [         1,          20,      '-276px -756px' ]
      }
    $.each(entries, function(ix,it){
      $reel.trigger('rowChange', Number(ix));
      equal( $reel.data('row'), it[0], 'Row '+ix+': Interpolated row');
      equal( $reel.data('frame'), it[1], 'Row '+ix+': Shifted frame');
      equal( $reel.css('backgroundPosition'), it[2], 'Row '+ix+': Sprite CSS background position');
    });
    start();
  });

  asyncTest( 'Multi-row: Uneven rows frame shifting (3 rows)', function(){
		expect(9);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        footage: 4,
        frame: 5,
        rows: 3,
        row: 3
      }),
      /*
      Sprite layout:

      A1 A2 A3 A4                    1  2  3  4
      A5 A6 B1 B2                    5  6  7  8
      B3 B4 B5 B6  in real frames »  9 10 11 12
      C1 C2 C3 C4                   13 14 15 16
      C5 C6                         17 18

      (letter ~ row; number ~ frame)
      */
      entries= {
        '3': [         1,          17,      '0px -504px' ],
        '2': [       0.5,          11,      '-552px -252px' ],
        '1': [         0,           5,      '0px -126px' ]
      }
    $.each(entries, function(ix,it){
      $reel.trigger('rowChange', Number(ix));
      equal( $reel.data('row'), it[0], 'Row '+ix+': Interpolated row');
      equal( $reel.data('frame'), it[1], 'Row '+ix+': Shifted frame');
      equal( $reel.css('backgroundPosition'), it[2], 'Row '+ix+': Sprite CSS background position');
    });
    start();
  });

  asyncTest( 'Multi-row: Immune to `backwards` value', function(){
		expect(6);
    var
      selector= '#image',
      $reel= $(selector).reel({
        frames: 6,
        footage: 4,
        frame: 5,
        rows: 3,
        row: 3
      })
    equal( $reel.data('row'), 1);
    equal( $reel.data('frame'), 17);
    equal( $reel.data('backwards'), false);

    $reel.data('backwards', true);
    equal( $reel.data('row'), 1);
    equal( $reel.data('frame'), 17);
    equal( $reel.data('backwards'), true);
    start();
  });

})(jQuery);<|endoftext|>