<commit_before>/*
Copyright (c) 2014 Dominik Moritz

This file is part of the leaflet locate control. It is licensed under the MIT license.
You can find the project at: https://github.com/domoritz/leaflet-locatecontrol
*/
L.Control.Locate = L.Control.extend({
    options: {
        position: 'topleft',
        drawCircle: true,
        follow: false,  // follow with zoom and pan the user's location
        stopFollowingOnDrag: false, // if follow is true, stop following when map is dragged (deprecated)
        // if true locate control remains active on click even if the user's location is in view.
        // clicking control will just pan to location
        remainActive: false,
        markerClass: L.circleMarker, // L.circleMarker or L.marker
        // range circle
        circleStyle: {
            color: '#136AEC',
            fillColor: '#136AEC',
            fillOpacity: 0.15,
            weight: 2,
            opacity: 0.5
        },
        // inner marker
        markerStyle: {
            color: '#136AEC',
            fillColor: '#2A93EE',
            fillOpacity: 0.7,
            weight: 2,
            opacity: 0.9,
            radius: 5
        },
        // changes to range circle and inner marker while following
        // it is only necessary to provide the things that should change
        followCircleStyle: {},
        followMarkerStyle: {
            //color: '#FFA500',
            //fillColor: '#FFB000'
        },
        icon: 'fa fa-map-marker',  // fa-location-arrow or fa-map-marker
        iconLoading: 'fa fa-spinner fa-spin',
        circlePadding: [0, 0],
        metric: true,
        onLocationError: function(err) {
            // this event is called in case of any location error
            // that is not a time out error.
            alert(err.message);
        },
        onLocationOutsideMapBounds: function(control) {
            // this event is repeatedly called when the location changes
            control.stopLocate();
            alert(control.options.strings.outsideMapBoundsMsg);
        },
        setView: true, // automatically sets the map view to the user's location
        // keep the current map zoom level when displaying the user's location. (if 'false', use maxZoom)
        keepCurrentZoomLevel: false,
        showPopup: true, // display a popup when the user click on the inner marker
        strings: {
            title: "Show me where I am",
            popup: "You are within {distance} {unit} from this point",
            outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
        },
        locateOptions: {
            maxZoom: Infinity,
            watch: true  // if you overwrite this, visualization cannot be updated
        }
    },

    initialize: function (options) {
        for (var i in options) {
            if (typeof this.options[i] === 'object') {
                L.extend(this.options[i], options[i]);
            } else {
                this.options[i] = options[i];
            }
        }
    },

    onAdd: function (map) {
        var container = L.DomUtil.create('div',
            'leaflet-control-locate leaflet-bar leaflet-control');

        var self = this;
        this._layer = new L.LayerGroup();
        this._layer.addTo(map);
        this._event = undefined;

        this._locateOptions = this.options.locateOptions;
        L.extend(this._locateOptions, this.options.locateOptions);
        L.extend(this._locateOptions, {
            setView: false // have to set this to false because we have to
                           // do setView manually
        });

        // extend the follow marker style and circle from the normal style
        var tmp = {};
        L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
        this.options.followMarkerStyle = tmp;
        tmp = {};
        L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
        this.options.followCircleStyle = tmp;

        this._link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
        this._link.href = '#';
        this._link.title = this.options.strings.title;

        this._icon = L.DomUtil.create('span', this.options.icon, this._link);

        L.DomEvent
            .on(this._link, 'click', L.DomEvent.stopPropagation)
            .on(this._link, 'click', L.DomEvent.preventDefault)
            .on(this._link, 'click', function() {
                var shouldStop = (self._event === undefined || map.getBounds().contains(self._event.latlng)
                    || !self.options.setView || isOutsideMapBounds());
                if (!self.options.remainActive && (self._active && shouldStop)) {
                    stopLocate();
                } else {
                    locate();
                }
            })
            .on(this._link, 'dblclick', L.DomEvent.stopPropagation);

        var locate = function () {
            if (self.options.setView) {
                self._locateOnNextLocationFound = true;
            }
            if(!self._active) {
                map.locate(self._locateOptions);
            }
            self._active = true;
            if (self.options.follow) {
                startFollowing();
            }
            if (!self._event) {
                setClasses('requesting');
            } else {
                visualizeLocation();
            }
        };

        var onLocationFound = function (e) {
            // no need to do anything if the location has not changed
            if (self._event &&
                (self._event.latlng.lat === e.latlng.lat &&
                 self._event.latlng.lng === e.latlng.lng &&
                 self._event.accuracy === e.accuracy)) {
                return;
            }

            if (!self._active) {
                return;
            }

            self._event = e;

            if (self.options.follow && self._following) {
                self._locateOnNextLocationFound = true;
            }

            visualizeLocation();
        };

        var startFollowing = function() {
            map.fire('startfollowing', self);
            self._following = true;
            if (self.options.stopFollowingOnDrag) {
                map.on('dragstart', stopFollowing);
            }
        };

        var stopFollowing = function() {
            map.fire('stopfollowing', self);
            self._following = false;
            if (self.options.stopFollowingOnDrag) {
                map.off('dragstart', stopFollowing);
            }
            setContainerStyle();
        };

        var isOutsideMapBounds = function () {
            if (self._event === undefined)
                return false;
            return map.options.maxBounds &&
                !map.options.maxBounds.contains(self._event.latlng);
        };

        var visualizeLocation = function() {
            if (self._event.accuracy === undefined)
                self._event.accuracy = 0;

            var radius = self._event.accuracy;
            if (self._locateOnNextLocationFound) {
                if (isOutsideMapBounds()) {
                    self.options.onLocationOutsideMapBounds(self);
                } else {
                    map.fitBounds(self._event.bounds, {
                        padding: self.options.circlePadding,
                        maxZoom: self.options.keepCurrentZoomLevel ? map.getZoom() : self._locateOptions.maxZoom
                    });
                }
                self._locateOnNextLocationFound = false;
            }

            // circle with the radius of the location's accuracy
            var style, o;
            if (self.options.drawCircle) {
                if (self._following) {
                    style = self.options.followCircleStyle;
                } else {
                    style = self.options.circleStyle;
                }

                if (!self._circle) {
                    self._circle = L.circle(self._event.latlng, radius, style)
                        .addTo(self._layer);
                } else {
                    self._circle.setLatLng(self._event.latlng).setRadius(radius);
                    for (o in style) {
                        self._circle.options[o] = style[o];
                    }
                }
            }

            var distance, unit;
            if (self.options.metric) {
                distance = radius.toFixed(0);
                unit = "meters";
            } else {
                distance = (radius * 3.2808399).toFixed(0);
                unit = "feet";
            }

            // small inner marker
            var mStyle;
            if (self._following) {
                mStyle = self.options.followMarkerStyle;
            } else {
                mStyle = self.options.markerStyle;
            }

            if (!self._marker) {
                self._marker = self.options.markerClass(self._event.latlng, mStyle)
                    .addTo(self._layer);
            } else {
                self._marker.setLatLng(self._event.latlng);
                for (o in mStyle) {
                    self._marker.options[o] = mStyle[o];
                }
            }

			var t = self.options.strings.popup;
            if (self.options.showPopup && t) {
              self._marker.bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
                  ._popup.setLatLng(self._event.latlng);
            }

            setContainerStyle();
        };

        var setContainerStyle = function() {
            if (!self._container)
                return;
            if (self._following) {
                setClasses('following');
            } else {
                setClasses('active');
            }
        };

        var setClasses = function(state) {
            if (state == 'requesting') {
                L.DomUtil.removeClasses(self._container, "active following");
                L.DomUtil.addClasses(self._container, "requesting");

                L.DomUtil.removeClasses(self._icon, self.options.icon);
                L.DomUtil.addClasses(self._icon, self.options.iconLoading);
            } else if (state == 'active') {
                L.DomUtil.removeClasses(self._container, "requesting following");
                L.DomUtil.addClasses(self._container, "active");

                L.DomUtil.removeClasses(self._icon, self.options.iconLoading);
                L.DomUtil.addClasses(self._icon, self.options.icon);
            } else if (state == 'following') {
                L.DomUtil.removeClasses(self._container, "requesting");
                L.DomUtil.addClasses(self._container, "active following");

                L.DomUtil.removeClasses(self._icon, self.options.iconLoading);
                L.DomUtil.addClasses(self._icon, self.options.icon);
            }
        };

        var resetVariables = function() {
            self._active = false;
            self._locateOnNextLocationFound = self.options.setView;
            self._following = false;
        };

        resetVariables();

        var stopLocate = function() {
            map.stopLocate();
            map.off('dragstart', stopFollowing);
            if (self.options.follow && self._following) {
                stopFollowing();
            }

            L.DomUtil.removeClass(self._container, "requesting");
            L.DomUtil.removeClass(self._container, "active");
            L.DomUtil.removeClass(self._container, "following");
            resetVariables();

            self._layer.clearLayers();
            self._marker = undefined;
            self._circle = undefined;
        };

        var onLocationError = function (err) {
            // ignore time out error if the location is watched
            if (err.code == 3 && self._locateOptions.watch) {
                return;
            }

            stopLocate();
            self.options.onLocationError(err);
        };

        // event hooks
        map.on('locationfound', onLocationFound, self);
        map.on('locationerror', onLocationError, self);
        map.on('unload', stopLocate, self);

        // make locate functions available to outside world
        this.locate = locate;
        this.stopLocate = stopLocate;
        this.stopFollowing = stopFollowing;

        return container;
    }
});

L.Map.addInitHook(function () {
    if (this.options.locateControl) {
        this.locateControl = L.control.locate();
        this.addControl(this.locateControl);
    }
});

L.control.locate = function (options) {
    return new L.Control.Locate(options);
};

(function(){
  // leaflet.js raises bug when trying to addClass / removeClass multiple classes at once
  // Let's create a wrapper on it which fixes it.
  var LDomUtilApplyClassesMethod = function(method, element, classNames) {
    classNames = classNames.split(' ');
    classNames.forEach(function(className) {
        L.DomUtil[method].call(this, element, className);
    });
  };

  L.DomUtil.addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); };
  L.DomUtil.removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); };
})();
<commit_msg>AMD support<commit_after>/*
Copyright (c) 2014 Dominik Moritz

This file is part of the leaflet locate control. It is licensed under the MIT license.
You can find the project at: https://github.com/domoritz/leaflet-locatecontrol
*/
(function (factory) {
    // define an AMD module that relies on 'leaflet'
    if (typeof define === 'function' && define.amd) {
        define(['leaflet'], factory);

    // define a Common JS module that relies on 'leaflet'
    } else if (typeof exports === 'object') {
        module.exports = factory(require('leaflet'));

    }

    // attach your plugin to the global 'L' variable
    if(typeof window !== 'undefined' && window.L){
        window.L.Locate = factory(L);
    }

} (function (L){
    L.Control.Locate = L.Control.extend({
        options: {
            position: 'topleft',
            drawCircle: true,
            follow: false,  // follow with zoom and pan the user's location
            stopFollowingOnDrag: false, // if follow is true, stop following when map is dragged (deprecated)
            // if true locate control remains active on click even if the user's location is in view.
            // clicking control will just pan to location
            remainActive: false,
            markerClass: L.circleMarker, // L.circleMarker or L.marker
            // range circle
            circleStyle: {
                color: '#136AEC',
                fillColor: '#136AEC',
                fillOpacity: 0.15,
                weight: 2,
                opacity: 0.5
            },
            // inner marker
            markerStyle: {
                color: '#136AEC',
                fillColor: '#2A93EE',
                fillOpacity: 0.7,
                weight: 2,
                opacity: 0.9,
                radius: 5
            },
            // changes to range circle and inner marker while following
            // it is only necessary to provide the things that should change
            followCircleStyle: {},
            followMarkerStyle: {
                //color: '#FFA500',
                //fillColor: '#FFB000'
            },
            icon: 'fa fa-map-marker',  // fa-location-arrow or fa-map-marker
            iconLoading: 'fa fa-spinner fa-spin',
            circlePadding: [0, 0],
            metric: true,
            onLocationError: function(err) {
                // this event is called in case of any location error
                // that is not a time out error.
                alert(err.message);
            },
            onLocationOutsideMapBounds: function(control) {
                // this event is repeatedly called when the location changes
                control.stopLocate();
                alert(control.options.strings.outsideMapBoundsMsg);
            },
            setView: true, // automatically sets the map view to the user's location
            // keep the current map zoom level when displaying the user's location. (if 'false', use maxZoom)
            keepCurrentZoomLevel: false,
            showPopup: true, // display a popup when the user click on the inner marker
            strings: {
                title: "Show me where I am",
                popup: "You are within {distance} {unit} from this point",
                outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
            },
            locateOptions: {
                maxZoom: Infinity,
                watch: true  // if you overwrite this, visualization cannot be updated
            }
        },

        initialize: function (options) {
            L.Map.addInitHook(function () {
                if (this.options.locateControl) {
                    this.locateControl = L.control.locate();
                    this.addControl(this.locateControl);
                }
            });

            for (var i in options) {
                if (typeof this.options[i] === 'object') {
                    L.extend(this.options[i], options[i]);
                } else {
                    this.options[i] = options[i];
                }
            }
        },

        onAdd: function (map) {
            var container = L.DomUtil.create('div',
                'leaflet-control-locate leaflet-bar leaflet-control');

            var self = this;
            this._layer = new L.LayerGroup();
            this._layer.addTo(map);
            this._event = undefined;

            this._locateOptions = this.options.locateOptions;
            L.extend(this._locateOptions, this.options.locateOptions);
            L.extend(this._locateOptions, {
                setView: false // have to set this to false because we have to
                               // do setView manually
            });

            // extend the follow marker style and circle from the normal style
            var tmp = {};
            L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
            this.options.followMarkerStyle = tmp;
            tmp = {};
            L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
            this.options.followCircleStyle = tmp;

            this._link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
            this._link.href = '#';
            this._link.title = this.options.strings.title;

            this._icon = L.DomUtil.create('span', this.options.icon, this._link);

            L.DomEvent
                .on(this._link, 'click', L.DomEvent.stopPropagation)
                .on(this._link, 'click', L.DomEvent.preventDefault)
                .on(this._link, 'click', function() {
                    var shouldStop = (self._event === undefined || map.getBounds().contains(self._event.latlng)
                        || !self.options.setView || isOutsideMapBounds());
                    if (!self.options.remainActive && (self._active && shouldStop)) {
                        stopLocate();
                    } else {
                        locate();
                    }
                })
                .on(this._link, 'dblclick', L.DomEvent.stopPropagation);

            var locate = function () {
                if (self.options.setView) {
                    self._locateOnNextLocationFound = true;
                }
                if(!self._active) {
                    map.locate(self._locateOptions);
                }
                self._active = true;
                if (self.options.follow) {
                    startFollowing();
                }
                if (!self._event) {
                    setClasses('requesting');
                } else {
                    visualizeLocation();
                }
            };

            var onLocationFound = function (e) {
                // no need to do anything if the location has not changed
                if (self._event &&
                    (self._event.latlng.lat === e.latlng.lat &&
                     self._event.latlng.lng === e.latlng.lng &&
                     self._event.accuracy === e.accuracy)) {
                    return;
                }

                if (!self._active) {
                    return;
                }

                self._event = e;

                if (self.options.follow && self._following) {
                    self._locateOnNextLocationFound = true;
                }

                visualizeLocation();
            };

            var startFollowing = function() {
                map.fire('startfollowing', self);
                self._following = true;
                if (self.options.stopFollowingOnDrag) {
                    map.on('dragstart', stopFollowing);
                }
            };

            var stopFollowing = function() {
                map.fire('stopfollowing', self);
                self._following = false;
                if (self.options.stopFollowingOnDrag) {
                    map.off('dragstart', stopFollowing);
                }
                setContainerStyle();
            };

            var isOutsideMapBounds = function () {
                if (self._event === undefined)
                    return false;
                return map.options.maxBounds &&
                    !map.options.maxBounds.contains(self._event.latlng);
            };

            var visualizeLocation = function() {
                if (self._event.accuracy === undefined)
                    self._event.accuracy = 0;

                var radius = self._event.accuracy;
                if (self._locateOnNextLocationFound) {
                    if (isOutsideMapBounds()) {
                        self.options.onLocationOutsideMapBounds(self);
                    } else {
                        map.fitBounds(self._event.bounds, {
                            padding: self.options.circlePadding,
                            maxZoom: self.options.keepCurrentZoomLevel ? map.getZoom() : self._locateOptions.maxZoom
                        });
                    }
                    self._locateOnNextLocationFound = false;
                }

                // circle with the radius of the location's accuracy
                var style, o;
                if (self.options.drawCircle) {
                    if (self._following) {
                        style = self.options.followCircleStyle;
                    } else {
                        style = self.options.circleStyle;
                    }

                    if (!self._circle) {
                        self._circle = L.circle(self._event.latlng, radius, style)
                            .addTo(self._layer);
                    } else {
                        self._circle.setLatLng(self._event.latlng).setRadius(radius);
                        for (o in style) {
                            self._circle.options[o] = style[o];
                        }
                    }
                }

                var distance, unit;
                if (self.options.metric) {
                    distance = radius.toFixed(0);
                    unit = "meters";
                } else {
                    distance = (radius * 3.2808399).toFixed(0);
                    unit = "feet";
                }

                // small inner marker
                var mStyle;
                if (self._following) {
                    mStyle = self.options.followMarkerStyle;
                } else {
                    mStyle = self.options.markerStyle;
                }

                if (!self._marker) {
                    self._marker = self.options.markerClass(self._event.latlng, mStyle)
                        .addTo(self._layer);
                } else {
                    self._marker.setLatLng(self._event.latlng);
                    for (o in mStyle) {
                        self._marker.options[o] = mStyle[o];
                    }
                }

		        var t = self.options.strings.popup;
                if (self.options.showPopup && t) {
                  self._marker.bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
                      ._popup.setLatLng(self._event.latlng);
                }

                setContainerStyle();
            };

            var setContainerStyle = function() {
                if (!self._container)
                    return;
                if (self._following) {
                    setClasses('following');
                } else {
                    setClasses('active');
                }
            };

            var setClasses = function(state) {
                if (state == 'requesting') {
                    L.DomUtil.removeClasses(self._container, "active following");
                    L.DomUtil.addClasses(self._container, "requesting");

                    L.DomUtil.removeClasses(self._icon, self.options.icon);
                    L.DomUtil.addClasses(self._icon, self.options.iconLoading);
                } else if (state == 'active') {
                    L.DomUtil.removeClasses(self._container, "requesting following");
                    L.DomUtil.addClasses(self._container, "active");

                    L.DomUtil.removeClasses(self._icon, self.options.iconLoading);
                    L.DomUtil.addClasses(self._icon, self.options.icon);
                } else if (state == 'following') {
                    L.DomUtil.removeClasses(self._container, "requesting");
                    L.DomUtil.addClasses(self._container, "active following");

                    L.DomUtil.removeClasses(self._icon, self.options.iconLoading);
                    L.DomUtil.addClasses(self._icon, self.options.icon);
                }
            };

            var resetVariables = function() {
                self._active = false;
                self._locateOnNextLocationFound = self.options.setView;
                self._following = false;
            };

            resetVariables();

            var stopLocate = function() {
                map.stopLocate();
                map.off('dragstart', stopFollowing);
                if (self.options.follow && self._following) {
                    stopFollowing();
                }

                L.DomUtil.removeClass(self._container, "requesting");
                L.DomUtil.removeClass(self._container, "active");
                L.DomUtil.removeClass(self._container, "following");
                resetVariables();

                self._layer.clearLayers();
                self._marker = undefined;
                self._circle = undefined;
            };

            var onLocationError = function (err) {
                // ignore time out error if the location is watched
                if (err.code == 3 && self._locateOptions.watch) {
                    return;
                }

                stopLocate();
                self.options.onLocationError(err);
            };

            // event hooks
            map.on('locationfound', onLocationFound, self);
            map.on('locationerror', onLocationError, self);
            map.on('unload', stopLocate, self);

            // make locate functions available to outside world
            this.locate = locate;
            this.stopLocate = stopLocate;
            this.stopFollowing = stopFollowing;

            return container;
        }
    });

    L.Map.addInitHook(function () {
        if (this.options.locateControl) {
            this.locateControl = L.control.locate();
            this.addControl(this.locateControl);
        }
    });

    L.control.locate = function (options) {
        return new L.Control.Locate(options);
    };

    (function(){
      // leaflet.js raises bug when trying to addClass / removeClass multiple classes at once
      // Let's create a wrapper on it which fixes it.
      var LDomUtilApplyClassesMethod = function(method, element, classNames) {
        classNames = classNames.split(' ');
        classNames.forEach(function(className) {
            L.DomUtil[method].call(this, element, className);
        });
      };

      L.DomUtil.addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); };
      L.DomUtil.removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); };
    })();
    return L.Control.Locate;
}, window));
<|endoftext|>


<commit_before>/* eslint-env mocha */
/* @flow */

const Promise = require('bluebird')
const fse = require('fs-extra')
const _ = require('lodash')
const path = require('path')
const chai = require('chai')
const chaiLike = require('chai-like')
chai.use(chaiLike)
chai.Should()

const config = require('../../core/config')

const {
  disabledScenarioTest,
  init,
  loadFSEventFiles,
  loadAtomCaptures,
  runActions,
  scenarios,
  runWithBreakpoints,
  runWithStoppedClient
} = require('../support/helpers/scenarios')
const configHelpers = require('../support/helpers/config')
const cozyHelpers = require('../support/helpers/cozy')
const TestHelpers = require('../support/helpers')
const pouchHelpers = require('../support/helpers/pouch')
const remoteCaptureHelpers = require('../../dev/capture/remote')

const { platform } = process

const logger = require('../../core/utils/logger')
const log = new logger({ component: 'TEST' })

describe('Scenario', function () {
  let helpers

  beforeEach(configHelpers.createConfig)
  beforeEach(configHelpers.registerClient)
  beforeEach(pouchHelpers.createDatabase)
  beforeEach(cozyHelpers.deleteAll)
  beforeEach('set up outside dir', async function () {
    await fse.emptyDir(path.resolve(path.join(this.syncPath, '..', 'outside')))
  })
  beforeEach(async function () {
    helpers = TestHelpers.init(this)

    // TODO: helpers.setup()
    await helpers.local.setupTrash()
    await helpers.remote.ignorePreviousChanges()
  })

  afterEach(async function () {
    await helpers.stop()
  })
  afterEach(pouchHelpers.cleanDatabase)
  afterEach(configHelpers.cleanConfig)

  for (let scenario of scenarios) {
    if (scenario.platforms && !scenario.platforms.includes(platform)) {
      it.skip(`test/scenarios/${scenario.name}/  (skip on ${platform})`, () => {})
      continue
    }

    if (scenario.side === 'remote') {
      it.skip(`test/scenarios/${scenario.name}/local/  (skip remote only test)`, () => {})
    } else if (!runWithStoppedClient()) {
      for (let atomCapture of loadAtomCaptures(scenario)) {
        const localTestName = `test/scenarios/${scenario.name}/atom/${atomCapture.name}`
        if (config.watcherType() !== 'atom') {
          it.skip(localTestName, () => {})
          continue
        }

        if (atomCapture.disabled) {
          it.skip(`${localTestName}  (${atomCapture.disabled})`, () => {})
          continue
        }

        describe(localTestName, () => {
          if (scenario.init) {
            // Run the init phase outside the test itself to prevent timeouts on
            // long inits.
            // XXX: beforeEach is used to have access to async/await and helpers
            beforeEach(async () => {
              await init(
                scenario,
                helpers.pouch,
                helpers.local.syncDir.abspath,
                scenario.useCaptures ? atomCapture : undefined
              )
            })
          }

          it('', async function () {
            await runLocalAtom(scenario, atomCapture, helpers)
          })
        })
      }

      for (let eventsFile of loadFSEventFiles(scenario)) {
        const localTestName = `test/scenarios/${scenario.name}/local/${eventsFile.name}`
        if (config.watcherType() !== 'chokidar') {
          it.skip(localTestName, () => {})
          continue
        }
        if (eventsFile.disabled) {
          it.skip(`${localTestName}  (${eventsFile.disabled})`, () => {})
          continue
        }

        if (scenario.useCaptures) {
          const breakpoints = injectChokidarBreakpoints(eventsFile)

          for (let flushAfter of breakpoints) {
            it(localTestName + ' flushAfter=' + flushAfter, async function () {
              await runLocalChokidarWithCaptures(
                scenario,
                _.cloneDeep(eventsFile),
                flushAfter,
                helpers
              )
            })
          }
        } else {
          it(localTestName, async function () {
            await runLocalChokidarWithoutCaptures(
              scenario,
              _.cloneDeep(eventsFile),
              helpers
            )
          })
        }
      }
    } else {
      const stoppedTestName = `test/scenarios/${scenario.name}/local/stopped`
      const stoppedTestSkipped = shouldSkipLocalStopped(scenario)
      if (stoppedTestSkipped) {
        it.skip(`${stoppedTestName} (${stoppedTestSkipped})`, () => {})
      } else {
        it(stoppedTestName, async function () {
          this.timeout(3 * 60 * 1000)
          await runLocalStopped(scenario, helpers)
        })
      }
    }

    if (runWithStoppedClient()) continue
    const remoteTestName = `test/scenarios/${scenario.name}/remote/`
    const remoteTestSkipped = shouldSkipRemote(scenario)
    if (remoteTestSkipped) {
      it.skip(`${remoteTestName}  (${remoteTestSkipped})`, () => {})
      continue
    }

    it(remoteTestName, async function () {
      await runRemote(scenario, helpers)
    })
  }
})

function shouldSkipLocalStopped(scenario) {
  const disabled = disabledScenarioTest(scenario, 'stopped')
  if (disabled) {
    return disabled
  } else if (!runWithStoppedClient()) {
    return `STOPPED_CLIENT is not set`
  }
}

function shouldSkipRemote(scenario) {
  if (scenario.name.indexOf('outside') !== -1) {
    return 'skip outside case'
  }
  const disabled = disabledScenarioTest(scenario, 'remote')
  if (disabled) {
    return disabled
  } else if (scenario.side === 'local') {
    return 'skip local only test'
  }
}

function injectChokidarBreakpoints(eventsFile) {
  let breakpoints = []
  if (eventsFile.events[0] && eventsFile.events[0].breakpoints) {
    breakpoints = eventsFile.events[0].breakpoints
    eventsFile.events = eventsFile.events.slice(1)
  } else {
    // break between each events
    for (let i = 0; i < eventsFile.events.length; i++) breakpoints.push(i)
  }

  if (!runWithBreakpoints()) breakpoints = [0]
  return breakpoints
}

async function runLocalAtom(scenario, atomCapture, helpers) {
  if (scenario.useCaptures) {
    log.info('simulating atom start')
    await helpers.local.simulateAtomStart()
  } else {
    await helpers.local.side.watcher.start()
  }

  await runActions(scenario, helpers.local.syncDir.abspath, {
    skipWait: scenario.useCaptures
  })

  if (scenario.useCaptures) {
    await helpers.local.simulateAtomEvents(atomCapture.batches)
  } else {
    // Wait for all local events to be flushed or a 10s time limit in case no
    // events are fired.
    await Promise.race([
      new Promise(resolve => {
        helpers.local.side.events.on('local-end', resolve)
      }),
      new Promise(resolve => {
        setTimeout(resolve, 10000)
      })
    ])
    await helpers.local.side.watcher.stop()
  }

  await helpers.syncAll()
  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runLocalChokidarWithCaptures(
  scenario,
  eventsFile,
  flushAfter,
  helpers
) {
  if (scenario.init) {
    await init(
      scenario,
      helpers.pouch,
      helpers.local.syncDir.abspath,
      eventsFile
    )
  }

  const inodeChanges = await runActions(
    scenario,
    helpers.local.syncDir.abspath,
    {
      skipWait: true
    }
  )

  const eventsBefore = eventsFile.events.slice(0, flushAfter)
  const eventsAfter = eventsFile.events.slice(flushAfter)

  if (eventsBefore.length) {
    for (const event of eventsBefore.reverse()) {
      for (const change of inodeChanges) {
        if (
          change.ino &&
          event.stats &&
          event.stats.ino &&
          event.path === change.path
        ) {
          event.stats.ino = change.ino
          break
        }
      }
    }

    await helpers.local.simulateEvents(eventsBefore.reverse())
    await helpers.syncAll()
  }
  if (eventsAfter.length) {
    for (const event of eventsAfter.reverse()) {
      for (const change of inodeChanges) {
        if (
          change.ino &&
          event.stats &&
          event.stats.ino &&
          event.path === change.path
        ) {
          event.stats.ino = change.ino
          break
        }
      }
    }

    await helpers.local.simulateEvents(eventsAfter.reverse())
    await helpers.syncAll()
  }

  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runLocalChokidarWithoutCaptures(scenario, eventsFile, helpers) {
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
  }

  await helpers.local.side.watcher.start()

  await runActions(scenario, helpers.local.syncDir.abspath)

  // Wait for all local events to be flushed or a 10s time limit in case no
  // events are fired.
  await Promise.race([
    new Promise(resolve => {
      helpers.local.side.events.on('local-end', resolve)
    }),
    new Promise(resolve => {
      setTimeout(resolve, 10000)
    })
  ])
  await helpers.local.side.watcher.stop()

  await helpers.syncAll()
  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })

  // TODO: pull
}

async function runLocalStopped(scenario, helpers) {
  // TODO: Find why we need this to prevent random failures and fix it.
  await Promise.delay(500)
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
  }

  await runActions(scenario, helpers.local.syncDir.abspath, {
    skipWait: true
  })

  await helpers.local.scan()
  await helpers.syncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runRemote(scenario, helpers) {
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
    await helpers.remote.ignorePreviousChanges()
  }

  await remoteCaptureHelpers.runActions(scenario, cozyHelpers.cozy)

  await helpers.local.side.watcher.start()
  await helpers.remote.pullChanges()
  // TODO: Don't sync when scenario doesn't have target FS/trash assertions?
  await helpers.syncAll()
  // Wait for all local events to be flushed or a 10s time limit in case no
  // events are fired.
  await Promise.race([
    new Promise(resolve => {
      helpers.local.side.events.on('local-end', resolve)
    }),
    new Promise(resolve => {
      setTimeout(resolve, 10000)
    })
  ])
  await helpers.local.side.watcher.stop()
  await helpers.syncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: true,
    includeRemoteTrash: false
  })

  // TODO: Local trash assertions
}

async function verifyExpectations(
  scenario,
  helpers,
  { includeLocalTrash, includeRemoteTrash }
) {
  // TODO: Wrap in custom expectation
  if (scenario.expected) {
    const expectedLocalTree =
      scenario.expected.tree || scenario.expected.localTree
    const expectedRemoteTree =
      scenario.expected.tree || scenario.expected.remoteTree
    const expectedLocalTrash =
      scenario.expected.trash || scenario.expected.localTrash
    const expectedRemoteTrash =
      scenario.expected.trash || scenario.expected.remoteTrash
    const expected = {}
    const actual = {}

    const localTree = await helpers.local.treeWithoutTrash({ withIno: true })
    const pouchTree = await helpers.pouch.allDocs()
    const remoteTree = await helpers.remote.treeWithoutTrash()

    if (expectedLocalTree) {
      expected.localTree = expectedLocalTree.map(fpath => {
        const isFolder = fpath.endsWith('/')
        const localPath = path.normalize(isFolder ? fpath.slice(0, -1) : fpath)

        const pouchItem = pouchTree.find(
          // FIXME: we should not have to compare `localPath` with the
          // normalized form of `local.path` but `local.path` is normalized on
          // macOS since it's derived from local events' paths which are
          // normalized by `Chokidar.normalizedPaths` and so does not always
          // reflect the local path but the one that should be stored as the
          // main PouchDB record `path` attribute.
          d => d.local && d.local.path.normalize() === localPath.normalize()
        )
        return helpers.local.isIgnored(localPath, isFolder)
          ? { path: fpath }
          : {
              path: fpath,
              ino: pouchItem && pouchItem.local && pouchItem.local.ino,
              fileid: pouchItem && pouchItem.local && pouchItem.local.fileid // undefined if not running on Windows
            }
      })
      actual.localTree = localTree
    }
    if (expectedRemoteTree) {
      // TODO: fetch tree with id and rev to compare them
      expected.remoteTree = expectedRemoteTree
      actual.remoteTree = remoteTree
    }
    if (expectedLocalTrash && includeLocalTrash) {
      expected.localTrash = expectedLocalTrash
      actual.localTrash = await helpers.local.trash()
    }
    if (expectedRemoteTrash && includeRemoteTrash) {
      expected.remoteTrash = expectedRemoteTrash
      actual.remoteTrash = await helpers.remote.trash()
    }
    if (scenario.expected.contents) {
      expected.localContents = scenario.expected.contents
      expected.remoteContents = scenario.expected.contents
      actual.localContents = {}
      actual.remoteContents = {}
      for (const relpath of _.keys(scenario.expected.contents)) {
        actual.localContents[relpath] = await helpers.local
          .readFile(relpath)
          .catch(err => `Error Reading Local(${relpath}): ${err.message}`)
        actual.remoteContents[relpath] = await helpers.remote
          .readFile(relpath)
          .catch(err => `Error Reading Remote(${relpath}): ${err.message}`)
      }
    }

    actual.should.be.like(expected)
  }
}
<commit_msg>test: Set breakpoints only when needed<commit_after>/* eslint-env mocha */
/* @flow */

const Promise = require('bluebird')
const fse = require('fs-extra')
const _ = require('lodash')
const path = require('path')
const chai = require('chai')
const chaiLike = require('chai-like')
chai.use(chaiLike)
chai.Should()

const config = require('../../core/config')

const {
  disabledScenarioTest,
  init,
  loadFSEventFiles,
  loadAtomCaptures,
  runActions,
  scenarios,
  runWithBreakpoints,
  runWithStoppedClient
} = require('../support/helpers/scenarios')
const configHelpers = require('../support/helpers/config')
const cozyHelpers = require('../support/helpers/cozy')
const TestHelpers = require('../support/helpers')
const pouchHelpers = require('../support/helpers/pouch')
const remoteCaptureHelpers = require('../../dev/capture/remote')

const { platform } = process

const logger = require('../../core/utils/logger')
const log = new logger({ component: 'TEST' })

describe('Scenario', function () {
  let helpers

  beforeEach(configHelpers.createConfig)
  beforeEach(configHelpers.registerClient)
  beforeEach(pouchHelpers.createDatabase)
  beforeEach(cozyHelpers.deleteAll)
  beforeEach('set up outside dir', async function () {
    await fse.emptyDir(path.resolve(path.join(this.syncPath, '..', 'outside')))
  })
  beforeEach(async function () {
    helpers = TestHelpers.init(this)

    // TODO: helpers.setup()
    await helpers.local.setupTrash()
    await helpers.remote.ignorePreviousChanges()
  })

  afterEach(async function () {
    await helpers.stop()
  })
  afterEach(pouchHelpers.cleanDatabase)
  afterEach(configHelpers.cleanConfig)

  for (let scenario of scenarios) {
    if (scenario.platforms && !scenario.platforms.includes(platform)) {
      it.skip(`test/scenarios/${scenario.name}/  (skip on ${platform})`, () => {})
      continue
    }

    if (scenario.side === 'remote') {
      it.skip(`test/scenarios/${scenario.name}/local/  (skip remote only test)`, () => {})
    } else if (!runWithStoppedClient()) {
      for (let atomCapture of loadAtomCaptures(scenario)) {
        const localTestName = `test/scenarios/${scenario.name}/atom/${atomCapture.name}`
        if (config.watcherType() !== 'atom') {
          it.skip(localTestName, () => {})
          continue
        }

        if (atomCapture.disabled) {
          it.skip(`${localTestName}  (${atomCapture.disabled})`, () => {})
          continue
        }

        describe(localTestName, () => {
          if (scenario.init) {
            // Run the init phase outside the test itself to prevent timeouts on
            // long inits.
            // XXX: beforeEach is used to have access to async/await and helpers
            beforeEach(async () => {
              await init(
                scenario,
                helpers.pouch,
                helpers.local.syncDir.abspath,
                scenario.useCaptures ? atomCapture : undefined
              )
            })
          }

          it('', async function () {
            await runLocalAtom(scenario, atomCapture, helpers)
          })
        })
      }

      for (let eventsFile of loadFSEventFiles(scenario)) {
        const localTestName = `test/scenarios/${scenario.name}/local/${eventsFile.name}`
        if (config.watcherType() !== 'chokidar') {
          it.skip(localTestName, () => {})
          continue
        }
        if (eventsFile.disabled) {
          it.skip(`${localTestName}  (${eventsFile.disabled})`, () => {})
          continue
        }

        if (scenario.useCaptures) {
          const breakpoints = injectChokidarBreakpoints(eventsFile)

          for (let flushAfter of breakpoints) {
            it(localTestName + ' flushAfter=' + flushAfter, async function () {
              await runLocalChokidarWithCaptures(
                scenario,
                _.cloneDeep(eventsFile),
                flushAfter,
                helpers
              )
            })
          }
        } else {
          it(localTestName, async function () {
            await runLocalChokidarWithoutCaptures(
              scenario,
              _.cloneDeep(eventsFile),
              helpers
            )
          })
        }
      }
    } else {
      const stoppedTestName = `test/scenarios/${scenario.name}/local/stopped`
      const stoppedTestSkipped = shouldSkipLocalStopped(scenario)
      if (stoppedTestSkipped) {
        it.skip(`${stoppedTestName} (${stoppedTestSkipped})`, () => {})
      } else {
        it(stoppedTestName, async function () {
          this.timeout(3 * 60 * 1000)
          await runLocalStopped(scenario, helpers)
        })
      }
    }

    if (runWithStoppedClient()) continue
    const remoteTestName = `test/scenarios/${scenario.name}/remote/`
    const remoteTestSkipped = shouldSkipRemote(scenario)
    if (remoteTestSkipped) {
      it.skip(`${remoteTestName}  (${remoteTestSkipped})`, () => {})
      continue
    }

    it(remoteTestName, async function () {
      await runRemote(scenario, helpers)
    })
  }
})

function shouldSkipLocalStopped(scenario) {
  const disabled = disabledScenarioTest(scenario, 'stopped')
  if (disabled) {
    return disabled
  } else if (!runWithStoppedClient()) {
    return `STOPPED_CLIENT is not set`
  }
}

function shouldSkipRemote(scenario) {
  if (scenario.name.indexOf('outside') !== -1) {
    return 'skip outside case'
  }
  const disabled = disabledScenarioTest(scenario, 'remote')
  if (disabled) {
    return disabled
  } else if (scenario.side === 'local') {
    return 'skip local only test'
  }
}

function injectChokidarBreakpoints(eventsFile) {
  const { breakpoints = [] } = eventsFile.events[0] || {}

  if (breakpoints.length) {
    // Keep only actual events
    eventsFile.events = eventsFile.events.slice(1)
  }

  if (!runWithBreakpoints()) {
    // Flush only after all events were notified
    return [0]
  } else if (breakpoints.length) {
    // Flush after each requested breakpoint (i.e. specific numbers of events)
    return breakpoints
  } else {
    // Flush after each event
    return Object.keys(eventsFile.events)
  }
}

async function runLocalAtom(scenario, atomCapture, helpers) {
  if (scenario.useCaptures) {
    log.info('simulating atom start')
    await helpers.local.simulateAtomStart()
  } else {
    await helpers.local.side.watcher.start()
  }

  await runActions(scenario, helpers.local.syncDir.abspath, {
    skipWait: scenario.useCaptures
  })

  if (scenario.useCaptures) {
    await helpers.local.simulateAtomEvents(atomCapture.batches)
  } else {
    // Wait for all local events to be flushed or a 10s time limit in case no
    // events are fired.
    await Promise.race([
      new Promise(resolve => {
        helpers.local.side.events.on('local-end', resolve)
      }),
      new Promise(resolve => {
        setTimeout(resolve, 10000)
      })
    ])
    await helpers.local.side.watcher.stop()
  }

  await helpers.syncAll()
  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runLocalChokidarWithCaptures(
  scenario,
  eventsFile,
  flushAfter,
  helpers
) {
  if (scenario.init) {
    await init(
      scenario,
      helpers.pouch,
      helpers.local.syncDir.abspath,
      eventsFile
    )
  }

  const inodeChanges = await runActions(
    scenario,
    helpers.local.syncDir.abspath,
    {
      skipWait: true
    }
  )

  const eventsBefore = eventsFile.events.slice(0, flushAfter)
  const eventsAfter = eventsFile.events.slice(flushAfter)

  if (eventsBefore.length) {
    for (const event of eventsBefore.reverse()) {
      for (const change of inodeChanges) {
        if (
          change.ino &&
          event.stats &&
          event.stats.ino &&
          event.path === change.path
        ) {
          event.stats.ino = change.ino
          break
        }
      }
    }

    await helpers.local.simulateEvents(eventsBefore.reverse())
    await helpers.syncAll()
  }
  if (eventsAfter.length) {
    for (const event of eventsAfter.reverse()) {
      for (const change of inodeChanges) {
        if (
          change.ino &&
          event.stats &&
          event.stats.ino &&
          event.path === change.path
        ) {
          event.stats.ino = change.ino
          break
        }
      }
    }

    await helpers.local.simulateEvents(eventsAfter.reverse())
    await helpers.syncAll()
  }

  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runLocalChokidarWithoutCaptures(scenario, eventsFile, helpers) {
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
  }

  await helpers.local.side.watcher.start()

  await runActions(scenario, helpers.local.syncDir.abspath)

  // Wait for all local events to be flushed or a 10s time limit in case no
  // events are fired.
  await Promise.race([
    new Promise(resolve => {
      helpers.local.side.events.on('local-end', resolve)
    }),
    new Promise(resolve => {
      setTimeout(resolve, 10000)
    })
  ])
  await helpers.local.side.watcher.stop()

  await helpers.syncAll()
  await helpers.pullAndSyncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })

  // TODO: pull
}

async function runLocalStopped(scenario, helpers) {
  // TODO: Find why we need this to prevent random failures and fix it.
  await Promise.delay(500)
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
  }

  await runActions(scenario, helpers.local.syncDir.abspath, {
    skipWait: true
  })

  await helpers.local.scan()
  await helpers.syncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: false,
    includeRemoteTrash: true
  })
}

async function runRemote(scenario, helpers) {
  if (scenario.init) {
    await init(scenario, helpers.pouch, helpers.local.syncDir.abspath)
    await helpers.remote.ignorePreviousChanges()
  }

  await remoteCaptureHelpers.runActions(scenario, cozyHelpers.cozy)

  await helpers.local.side.watcher.start()
  await helpers.remote.pullChanges()
  // TODO: Don't sync when scenario doesn't have target FS/trash assertions?
  await helpers.syncAll()
  // Wait for all local events to be flushed or a 10s time limit in case no
  // events are fired.
  await Promise.race([
    new Promise(resolve => {
      helpers.local.side.events.on('local-end', resolve)
    }),
    new Promise(resolve => {
      setTimeout(resolve, 10000)
    })
  ])
  await helpers.local.side.watcher.stop()
  await helpers.syncAll()

  await verifyExpectations(scenario, helpers, {
    includeLocalTrash: true,
    includeRemoteTrash: false
  })

  // TODO: Local trash assertions
}

async function verifyExpectations(
  scenario,
  helpers,
  { includeLocalTrash, includeRemoteTrash }
) {
  // TODO: Wrap in custom expectation
  if (scenario.expected) {
    const expectedLocalTree =
      scenario.expected.tree || scenario.expected.localTree
    const expectedRemoteTree =
      scenario.expected.tree || scenario.expected.remoteTree
    const expectedLocalTrash =
      scenario.expected.trash || scenario.expected.localTrash
    const expectedRemoteTrash =
      scenario.expected.trash || scenario.expected.remoteTrash
    const expected = {}
    const actual = {}

    const localTree = await helpers.local.treeWithoutTrash({ withIno: true })
    const pouchTree = await helpers.pouch.allDocs()
    const remoteTree = await helpers.remote.treeWithoutTrash()

    if (expectedLocalTree) {
      expected.localTree = expectedLocalTree.map(fpath => {
        const isFolder = fpath.endsWith('/')
        const localPath = path.normalize(isFolder ? fpath.slice(0, -1) : fpath)

        const pouchItem = pouchTree.find(
          // FIXME: we should not have to compare `localPath` with the
          // normalized form of `local.path` but `local.path` is normalized on
          // macOS since it's derived from local events' paths which are
          // normalized by `Chokidar.normalizedPaths` and so does not always
          // reflect the local path but the one that should be stored as the
          // main PouchDB record `path` attribute.
          d => d.local && d.local.path.normalize() === localPath.normalize()
        )
        return helpers.local.isIgnored(localPath, isFolder)
          ? { path: fpath }
          : {
              path: fpath,
              ino: pouchItem && pouchItem.local && pouchItem.local.ino,
              fileid: pouchItem && pouchItem.local && pouchItem.local.fileid // undefined if not running on Windows
            }
      })
      actual.localTree = localTree
    }
    if (expectedRemoteTree) {
      // TODO: fetch tree with id and rev to compare them
      expected.remoteTree = expectedRemoteTree
      actual.remoteTree = remoteTree
    }
    if (expectedLocalTrash && includeLocalTrash) {
      expected.localTrash = expectedLocalTrash
      actual.localTrash = await helpers.local.trash()
    }
    if (expectedRemoteTrash && includeRemoteTrash) {
      expected.remoteTrash = expectedRemoteTrash
      actual.remoteTrash = await helpers.remote.trash()
    }
    if (scenario.expected.contents) {
      expected.localContents = scenario.expected.contents
      expected.remoteContents = scenario.expected.contents
      actual.localContents = {}
      actual.remoteContents = {}
      for (const relpath of _.keys(scenario.expected.contents)) {
        actual.localContents[relpath] = await helpers.local
          .readFile(relpath)
          .catch(err => `Error Reading Local(${relpath}): ${err.message}`)
        actual.remoteContents[relpath] = await helpers.remote
          .readFile(relpath)
          .catch(err => `Error Reading Remote(${relpath}): ${err.message}`)
      }
    }

    actual.should.be.like(expected)
  }
}
<|endoftext|>


<commit_before>'use strict';

var chai = require('chai'), _ = require('lodash'), q = require('q'), zeit = require('../'), moment = require('moment'), assert = chai.assert;

chai.use(require('chai-timers'));

describe('scheduler', function () {

    function EventCapture(emitter) {
        var events = {};
        return {
            listenTo: function () {
                _.each(arguments, function (name) {
                    events[name] = [];
                    emitter.on(name, function (event) {
                        events[name].push(event);
                    })
                })
            },
            captured: events
        };
    }

    describe('scheduling a resolved one-off promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'finish');

        it('triggers a scheduled promise', function (done) {
            var id = scheduler.schedule(function () {
                return q.resolve();
            }, moment.duration(10), 'some name');
            _.defer(function () {
                assert.deepEqual(clock.triggerAll(), [scheduler.activeSchedules()[id].clockId]);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 1);
            assert.deepEqual(events.captured['finish'].length, 1);
        });

        it('cancels the schedule once run', function () {
            assert.equal(clock.triggerAll().length, 0);
        });
    });

    describe('scheduling a rejected one-off promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'error');

        it('triggers a scheduled promise', function (done) {
            var id = scheduler.schedule(function () {
                return q.reject();
            }, moment.duration(10), 'some name');
            var idsRun = clock.triggerAll();
            _.defer(function () {
                assert.deepEqual([scheduler.activeSchedules()[id].clockId], idsRun);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 1);
            assert.deepEqual(events.captured['error'].length, 1);
        });

        it('cancels the schedule once run', function () {
            assert.equal(clock.triggerAll().length, 0);
        });
    });

    describe('scheduling a repeating promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'finish');

        var id;
        var hasRun = 0;

        it('triggers a repeating promise right away', function () {
            id = scheduler.scheduleEvery(function () {
                hasRun++;
                return q.resolve();
            }, moment.duration(10), 'some name');
            _.defer(function () {
                assert.equal(hasRun, 1);
            })
        });

        it('triggers a repeating promise again', function (done) {
            clock.triggerAll();
            _.defer(function () {
                assert.equal(hasRun, 2);
                assert.notEqual([scheduler.activeSchedules()[id]], undefined);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 2);
            assert.deepEqual(events.captured['finish'].length, 2);
        });
    });

    describe('scheduling a repeating rejected promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'error');

        var id;
        var hasRun = 0;

        it('triggers a rejected repeating promise right away', function () {
            id = scheduler.scheduleEvery(function () {
                hasRun++;
                return q.reject();
            }, moment.duration(10), 'some name');
            _.defer(function () {
                assert.equal(hasRun, 1);
            })
        });

        it('triggers a repeating promise again', function (done) {
            clock.triggerAll();
            _.defer(function () {
                assert.equal(hasRun, 2);
                assert.notEqual([scheduler.activeSchedules()[id]], undefined);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 2);
            assert.deepEqual(events.captured['error'].length, 2);
        });
    });
});

<commit_msg>damned timeout tests for real clocks are tricky!<commit_after>'use strict';

var chai = require('chai'), _ = require('lodash'), q = require('q'), zeit = require('../'), moment = require('moment'), assert = chai.assert;

chai.use(require('chai-timers'));

describe('scheduler', function () {

    function EventCapture(emitter) {
        var events = {};
        return {
            listenTo: function () {
                _.each(arguments, function (name) {
                    events[name] = [];
                    emitter.on(name, function (event) {
                        events[name].push(event);
                    })
                })
            },
            captured: events
        };
    }

    describe('scheduling a resolved one-off promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'finish');

        it('triggers a scheduled promise', function (done) {
            var id = scheduler.schedule(function () {
                return q.resolve();
            }, moment.duration(10), 'some name');
            _.defer(function () {
                assert.deepEqual(clock.triggerAll(), [scheduler.activeSchedules()[id].clockId]);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 1);
            assert.deepEqual(events.captured['finish'].length, 1);
        });

        it('cancels the schedule once run', function () {
            assert.equal(clock.triggerAll().length, 0);
        });
    });

    describe('scheduling a rejected one-off promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'error');

        it('triggers a scheduled promise', function (done) {
            var id = scheduler.schedule(function () {
                return q.reject();
            }, moment.duration(10), 'some name');
            var idsRun = clock.triggerAll();
            _.defer(function () {
                assert.deepEqual([scheduler.activeSchedules()[id].clockId], idsRun);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 1);
            assert.deepEqual(events.captured['error'].length, 1);
        });

        it('cancels the schedule once run', function () {
            assert.equal(clock.triggerAll().length, 0);
        });
    });

    describe('scheduling a repeating promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'finish');

        var id;
        var hasRun = 0;

        it('triggers a repeating promise right away', function (done) {
            id = scheduler.scheduleEvery(function () {
                hasRun++;
                return q.resolve();
            }, moment.duration(10), 'some name');
            setTimeout(function () {
                assert.equal(hasRun, 1);
                done();
            }, 100);
        });

        it('triggers a repeating promise again', function (done) {
            clock.triggerAll();
            setTimeout(function () {
                assert.equal(hasRun, 2);
                assert.notEqual([scheduler.activeSchedules()[id]], undefined);
                done();
            }, 100);
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 2);
            assert.deepEqual(events.captured['finish'].length, 2);
        });
    });

    describe('scheduling a repeating rejected promise', function () {
        var clock = zeit.StubMomentClock();
        var scheduler = new zeit.PromiseScheduler(clock);
        var events = new EventCapture(scheduler);
        events.listenTo('start', 'error');

        var id;
        var hasRun = 0;

        it('triggers a rejected repeating promise right away', function () {
            id = scheduler.scheduleEvery(function () {
                hasRun++;
                return q.reject();
            }, moment.duration(10), 'some name');
            _.defer(function () {
                assert.equal(hasRun, 1);
            })
        });

        it('triggers a repeating promise again', function (done) {
            clock.triggerAll();
            _.defer(function () {
                assert.equal(hasRun, 2);
                assert.notEqual([scheduler.activeSchedules()[id]], undefined);
                done();
            })
        });

        it('emits the correct events', function () {
            assert.deepEqual(events.captured['start'].length, 2);
            assert.deepEqual(events.captured['error'].length, 2);
        });
    });
});

<|endoftext|>


<commit_before>/**
 * import(s)
 */

var Vue = require('../../node_modules/vue/dist/vue')
var plugin = require('../../index')

describe('min', function () {
  // TODO:

  it('should be ok', function (done) {
    expect(true).to.be(true)
    done()
  })
})

describe('max', function () {
  // TODO:

  it('should be ok', function (done) {
    expect(true).to.be(true)
    done()
  })
})
<commit_msg>Implement min/max validator spec<commit_after>/**
 * import(s)
 */

var Vue = require('../../node_modules/vue/dist/vue')
var validator = require('../../index')
var createInstance = require('./helper').createInstance

describe('min', function () {
  var vm, target

  before(function () {
    Vue.config.async = false
    Vue.use(validator)
  })

  after(function () {
    Vue.config.async = true
  })

  describe('model', function () {
    beforeEach(function () {
      vm = createInstance(
        '<input type="text" v-model="threshold" v-validate="min: 0">',
        { threshold: null }
      )
      target = vm._children[0]
    })

    describe('boundary value - 1', function () {
      beforeEach(function () {
        vm.threshold = -1
        vm._digest() // force update
      })

      it('should be true', function () {
        expect(target.validation.threshold.min).to.be(true)
      })
    })

    describe('boundary value', function () {
      beforeEach(function () {
        vm.threshold = 0
        vm._digest() // force update
      })

      it('should be false', function () {
        expect(target.validation.threshold.min).to.be(false)
      })
    })

    describe('boundary value + 1', function () {
      beforeEach(function () {
        vm.threshold = 1
        vm._digest() // force update
      })

      it('should be false', function () {
        expect(target.validation.threshold.min).to.be(false)
      })
    })

    describe('not numeric value', function () {
      beforeEach(function () {
        vm.threshold = 'hello'
        vm._digest() // force update
      })

      it('should be true', function () {
        expect(target.validation.threshold.min).to.be(true)
      })
    })
  })

  describe('expression', function () {
    describe('numeric', function () {
      beforeEach(function () {
        vm = createInstance(
          '<input type="text" v-model="threshold" v-validate="min: 50">',
          { threshold: 49 }
        )
        target = vm._children[0]
      })

      it('should be false', function () {
        expect(target.validation.threshold.min).to.be(false)
      })
    })

    describe('not numeric', function () {
      beforeEach(function () {
        vm = createInstance(
          '<input type="text" v-model="threshold" v-validate="min: \'hello\'">',
          { threshold: 49 }
        )
        target = vm._children[0]
      })

      it('should be true', function () {
        expect(target.validation.threshold.min).to.be(true)
      })
    })
  })
})

describe('max', function () {
  var vm, target

  before(function () {
    Vue.config.async = false
    Vue.use(validator)
  })

  after(function () {
    Vue.config.async = true
  })

  describe('model', function () {
    beforeEach(function () {
      vm = createInstance(
        '<input type="text" v-model="threshold" v-validate="max: 100">',
        { threshold: null }
      )
      target = vm._children[0]
    })

    describe('boundary value - 1', function () {
      beforeEach(function () {
        vm.threshold = 99
        vm._digest() // force update
      })

      it('should be false', function () {
        expect(target.validation.threshold.max).to.be(false)
      })
    })

    describe('boundary value', function () {
      beforeEach(function () {
        vm.threshold = 100
        vm._digest() // force update
      })

      it('should be false', function () {
        expect(target.validation.threshold.max).to.be(false)
      })
    })

    describe('boundary value + 1', function () {
      beforeEach(function () {
        vm.threshold = 101
        vm._digest() // force update
      })

      it('should be true', function () {
        expect(target.validation.threshold.max).to.be(true)
      })
    })

    describe('not numeric value', function () {
      beforeEach(function () {
        vm.threshold = 'hello'
        vm._digest() // force update
      })

      it('should be true', function () {
        expect(target.validation.threshold.max).to.be(true)
      })
    })
  })

  describe('expression', function () {
    describe('numeric', function () {
      beforeEach(function () {
        vm = createInstance(
          '<input type="text" v-model="threshold" v-validate="max: -2">',
          { threshold: -3 }
        )
        target = vm._children[0]
      })

      it('should be false', function () {
        expect(target.validation.threshold.max).to.be(false)
      })
    })

    describe('not numeric', function () {
      beforeEach(function () {
        vm = createInstance(
          '<input type="text" v-model="threshold" v-validate="max: \'hello\'">',
          { threshold: 100 }
        )
        target = vm._children[0]
      })

      it('should be true', function () {
        expect(target.validation.threshold.max).to.be(true)
      })
    })
  })
})
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'test',
  name: 'Indexable',

  properties: [
    {
      class: 'Int',
      name: 'int'
    },
    {
      class: 'Float',
      name: 'float'
    },
    {
      class: 'String',
      name: 'string'
    },
    {
      class: 'Date',
      name: 'date'
    },

  ]
});

describe('ValueIndex', function() {

});

<commit_msg>DAO tests: ValueIndex<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'test',
  name: 'Indexable',

  properties: [
    {
      class: 'Int',
      name: 'int'
    },
    {
      class: 'Float',
      name: 'float'
    },
    {
      class: 'String',
      name: 'string'
    },
    {
      class: 'Date',
      name: 'date'
    },

  ]
});

var createData1 = function createData1() {
  return [
    {
      int: 0,
      float: 0.0,
      string: "",
      date: new Date(0),
    },
    {
      int: 1,
      float: 1.1,
      string: "one!",
      date: new Date(1),
    },
  ].map(function(cfg) {
    return test.Indexable.create(cfg);
  });
}

describe('ValueIndex', function() {

  var data;
  var idx;

  beforeEach(function() {
    data = createData1();
    idx = foam.dao.index.ValueIndex.create();
  });

  it('stores a value', function() {
    idx.put(data[0]);
    expect(idx.get()).toBe(data[0]);
  });
  it('clears its value', function() {
    idx.put(data[0]);
    expect(idx.get()).toBe(data[0]);
    idx.remove();
    expect(idx.get()).toBeUndefined();
  });
  it('selects when skip and limit allow', function() {
    idx.put(data[0]);

    var sink = { put: function(o) { this.putted = o; } };

    idx.select(sink);
    expect(sink.putted).toBe(data[0]);
    delete sink.putted;

    var skip = [2];
    idx.select(sink, skip);
    expect(sink.putted).toBeUndefined(); // skipped
    expect(skip[0]).toEqual(1); // skip decremented
    delete sink.putted;

    var limit = [3];
    idx.select(sink, undefined, limit);
    expect(sink.putted).toBe(data[0]); // not at limit
    expect(limit[0]).toEqual(2); // limit decremented
    delete sink.putted;

    skip = [1];
    limit = [3];
    idx.select(sink, skip, limit);
    expect(sink.putted).toBeUndefined(); // skip
    expect(skip[0]).toEqual(0);
    expect(limit[0]).toEqual(3);
    delete sink.putted;
    idx.select(sink, skip, limit);
    expect(sink.putted).toBe(data[0]); // not at limit
    expect(limit[0]).toEqual(2);
    delete sink.putted;
    idx.select(sink, skip, limit);
    expect(sink.putted).toBe(data[0]); // not at limit
    expect(limit[0]).toEqual(1);
    delete sink.putted;
    idx.select(sink, skip, limit);
    expect(sink.putted).toBe(data[0]); // not at limit
    expect(limit[0]).toEqual(0);
    delete sink.putted;
    idx.select(sink, skip, limit);
    expect(sink.putted).toBeUndefined(); // at limit
    expect(limit[0]).toEqual(-1);
    delete sink.putted;

  });

  it('selects when predicate allows', function() {
    idx.put(data[0]);

    var sink = { put: function(o) { this.putted = o; } };

    var predicate = { f: function() { return false; } };
    idx.select(sink, undefined, undefined, undefined, predicate);
    expect(sink.putted).toBeUndefined();
    delete sink.putted;

    predicate = { f: function() { return true; } };
    idx.select(sink, undefined, undefined, undefined, predicate);
    expect(sink.putted).toBe(data[0]);
    delete sink.putted;

  });

});

<|endoftext|>


<commit_before>import test from 'ava';
import fs from 'fs';
import Sequelize from 'sequelize';
import { ExSwagger } from '../../src/swagger';
import * as exceptions from '../../src/exceptions';
import Entities from './../../src/entities';

test('Could get file lists', async(t) => {
  const files = await ExSwagger.scanFiles(`${__dirname}/_example/**/*.js`);
  t.true(files.includes(`${__dirname}/_example/controller.js`));
});

test('Could parse annotations', async(t) => {
  const annotationContainers = await ExSwagger.filesToAnnotationsContainers([`${__dirname}/_example/controller.js`]);
  t.is(annotationContainers.length, 1);
  t.is(annotationContainers[0].getAnnotations().length, 6);
});

test('Could parse swagger docs', async(t) => {
  const annotationContainers = await ExSwagger.filesToAnnotationsContainers([`${__dirname}/_example/controller.js`]);
  const fragments = annotationContainers[0].collectFragments();
  t.is(fragments.length, 4);
  t.true(fragments[0].isDefinition());
  t.true(typeof fragments[0].value === 'object');
  t.true(typeof fragments[0].description === 'string');
  t.true(fragments[1].isPath());
  t.true(typeof fragments[1].value === 'object');
  t.true(typeof fragments[1].description === 'string');
  t.true(fragments[2].isException());
  t.true(typeof fragments[2].value === 'string');
  t.true(typeof fragments[2].description === 'string');
  t.is(1, annotationContainers[0].collectYamlErrors().length);
  //TODO: test unknown
});
test('Scan exceptions', async(t) => {
  const scannedExceptions = await ExSwagger.scanExceptions(
    `${__dirname}/../../src/exceptions/**/*.js`, exceptions.StandardException
  );
  t.true(Object.keys(scannedExceptions).length >= 12);
});

test('default properties', async(t) => {
  const exSwagger = new ExSwagger({
    swaggerDocsTemplate: {},
    sourceRootPath: '/foo'
  });
  const states = exSwagger.getStates();
  t.true(states.sourceFilesPath.includes('/foo/**/*.js'));
});

test('Generate json file', async(t) => {
  const compileDistPath = `${__dirname}/_example/exports`;
  const exSwagger = new ExSwagger({
    compileDistPath,
    extraSourcePaths: [`${__dirname}/../../../lib/utils/**/*.js`],
    models: new Entities(`${__dirname}/../_demo_project/entities`, new Sequelize()),
    swaggerDocsTemplate: { definitions: {}, paths: {} },
    sourceRootPath: `${__dirname}/_example`
  });
  await exSwagger.exportJson();
  t.truthy(JSON.parse(fs.readFileSync(`${compileDistPath}/docs.json`)));
});
<commit_msg>fix swagger test<commit_after>import test from 'ava';
import path from 'path';
import fs from 'fs';
import Sequelize from 'sequelize';
import { ExSwagger } from '../../src/swagger';
import * as exceptions from '../../src/exceptions';
import Entities from './../../src/entities';

test('Could get file lists', async(t) => {
  const files = await ExSwagger.scanFiles(`${__dirname}/_example/**/*.js`);
  const ctrpath = `${__dirname}${path.sep}_example${path.sep}controller.js`.split(path.sep).join('/');
  t.true(files.includes(ctrpath));
});

test('Could parse annotations', async(t) => {
  const annotationContainers = await ExSwagger.filesToAnnotationsContainers([`${__dirname}/_example/controller.js`]);
  t.is(annotationContainers.length, 1);
  t.is(annotationContainers[0].getAnnotations().length, 6);
});

test('Could parse swagger docs', async(t) => {
  const annotationContainers = await ExSwagger.filesToAnnotationsContainers([`${__dirname}/_example/controller.js`]);
  const fragments = annotationContainers[0].collectFragments();
  t.is(fragments.length, 4);
  t.true(fragments[0].isDefinition());
  t.true(typeof fragments[0].value === 'object');
  t.true(typeof fragments[0].description === 'string');
  t.true(fragments[1].isPath());
  t.true(typeof fragments[1].value === 'object');
  t.true(typeof fragments[1].description === 'string');
  t.true(fragments[2].isException());
  t.true(typeof fragments[2].value === 'string');
  t.true(typeof fragments[2].description === 'string');
  t.is(1, annotationContainers[0].collectYamlErrors().length);
  //TODO: test unknown
});
test('Scan exceptions', async(t) => {
  const scannedExceptions = await ExSwagger.scanExceptions(
    `${__dirname}/../../src/exceptions/**/*.js`, exceptions.StandardException
  );
  t.true(Object.keys(scannedExceptions).length >= 12);
});

test('default properties', async(t) => {
  const exSwagger = new ExSwagger({
    swaggerDocsTemplate: {},
    sourceRootPath: '/foo'
  });
  const states = exSwagger.getStates();
  t.true(states.sourceFilesPath.includes('/foo/**/*.js'));
});

test('Generate json file', async(t) => {
  const compileDistPath = `${__dirname}/_example/exports`;
  const exSwagger = new ExSwagger({
    compileDistPath,
    extraSourcePaths: [`${__dirname}/../../../lib/utils/**/*.js`],
    models: new Entities(`${__dirname}/../_demo_project/entities`, new Sequelize()),
    swaggerDocsTemplate: { definitions: {}, paths: {} },
    sourceRootPath: `${__dirname}/_example`
  });
  await exSwagger.exportJson();
  t.truthy(JSON.parse(fs.readFileSync(`${compileDistPath}/docs.json`)));
});
<|endoftext|>