<commit_before>var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
var pos;
var destLatLng;
////////////////////////
//start the party
////////////////////////
function initialize() {
    var mapCanvas = document.getElementById('map-canvas'); //div in the html
    var mapOptions = {
      center: new google.maps.LatLng(36.1667, -86.7833), //NashVegas, TN
      zoom: 15,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      scrollwheel: false,
    };
    map = new google.maps.Map(mapCanvas, mapOptions);
    console.log('map', map); //this works, produces map object
    // Try HTML5 geolocation
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function (position) {
        pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
        var infowindow = new google.maps.InfoWindow({
          map: map,
          position: pos,
          pixelOffset: new google.maps.Size(0, -20),
          content: 'Start here'
        });
        var marker = new google.maps.Marker({
          position: pos,
          map: map,
          animation: google.maps.Animation.DROP,
          title: 'This is your geolocation'
        });
        //copied the next six lines from google, not gonna lie
        google.maps.event.addListener(marker, 'click', toggleBounce);

        function toggleBounce() {
          if (marker.getAnimation() !== null) {
            marker.setAnimation(null);
          } else {
            marker.setAnimation(google.maps.Animation.BOUNCE);
          }
        }
        google.maps.event.addListener(marker, 'click', function () {
          infowindow.open(map, marker); //this should be redone in the infobox class
        });
        map.setCenter(pos);
        console.log('map inside the geolocation function', map);
        //refactored code from weather app
        var btnWhereTo = document.querySelector('#btnWhereTo'); //creates the button element using the id from the button input
        btnWhereTo.onclick = function () {
          closestHooters();
          calcRoute(pos);
        };
        //adds the calcroute within geolocation because geolocation is the starting point
      }, function () {
        handleNoGeolocation(true);
      });
    } else {
      // Browser doesn't support Geolocation
      handleNoGeolocation(false);
    }
  }
  //////////////////////////////
  //destLatLng returns a string,
  //which breaks the app
  //////////////////////////////

function closestHooters() {
  var whereTo = document.querySelector('#whereTo').value.split(' ').join('+');
  var MAPS_URL = 'https://maps.googleapis.com/maps/api/geocode/json?address=';

  $.get(MAPS_URL + whereTo, function (data) {
    console.log('data', data); //now it sees this
    var hootersDistances = [];
    var destLat = data.results[0].geometry.location.lat;
    var destLatCoord = parseFloat(destLat); //otherwise returns a string
    console.log("destination latitude with parstFloat", destLat);
    var destLng = data.results[0].geometry.location.lng;
    var destLngCoord = parseFloat(destLng);
    var destLatLng = new google.maps.LatLng(destLatCoord, destLngCoord);
    console.log("both together; are these numbers?", destLatLng);
    var hooters2ndAve = new google.maps.LatLng(36.1618914, -86.789464);
    var hootersLebanonPike = new google.maps.LatLng(36.18633370000001, -86.63314799999999);
    var hootersLargoDrive = new google.maps.LatLng(36.081514, -86.7095413);
    console.log("both together and the 2nd Ave Hooters", destLatLng, "2nd Ave", hooters2ndAve, "are these objects?");
    var distSecondAve = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hooters2ndAve);
    var distLebanonPike = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hootersLebanonPike);
    var distLargoDrive = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hootersLargoDrive);
    hootersDistances.push(distLargoDrive, distLebanonPike, distSecondAve);
    var nearestHooters = Math.min.apply(Math, hootersDistances);
    var hootersWaypoint = function () {
      if (nearestHooters === distSecondAve) {
        return hooters2ndAve;
      } else if (nearestHooters === distLargoDrive) {
        return hootersLargoDrive;
      } else if (nearestHooters === distLebanonPike) {
        return hootersLebanonPike;
      }
    };hootersWaypoint();
    var hootersMarker = new google.maps.Marker({
      position: hooters2ndAve,
      map: map,
      icon: 'http://edwinacevedo.com/directionsFrom/img/hooters-marker.png'
  });});
}

function handleNoGeolocation(errorFlag) {
    if (errorFlag) {
      var content = 'Aw snap! Something went wrong with the Geolocation service. Sorry?';
    } else {
      var content = 'Aw, snap! Your browser doesn\'t support geolocation.';
    }
    var options = {
      map: map,
      position: new google.maps.LatLng(36.1667, 86.7833),
      content: content
    };
    var infowindow = new google.maps.InfoWindow(options);
    map.setCenter(options.position);
  }
  //////////////////////////////////
  //autocomplete for the search box
  /////////////////////////////////
var pac_input = document.getElementById('whereTo');
(function pacSelectFirst(input) {
  // store the original event binding function
  var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent;

  function addEventListenerWrapper(type, listener) {
    // Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
    // and then trigger the original listener.
    if (type == "keydown") {
      var orig_listener = listener;
      listener = function (event) {
        var suggestion_selected = $(".pac-item-selected").length > 0;
        if (event.which == 13 && !suggestion_selected) {
          var simulated_downarrow = $.Event("keydown", {
            keyCode: 40,
            which: 40
          });
          orig_listener.apply(input, [simulated_downarrow]);
        }
        orig_listener.apply(input, [event]);
      };
    }
    // add the modified listener
    _addEventListener.apply(input, [type, listener]);
  }
  if (input.addEventListener)
    input.addEventListener = addEventListenerWrapper;
  else if (input.attachEvent)
    input.attachEvent = addEventListenerWrapper;
})(pac_input);
$(function () {
  var autocomplete = new google.maps.places.Autocomplete(pac_input);
});
//////////////////////////////
//draw the route on the map
/////////////////////////////
function calcRoute(pos, destLatLng, hootersWaypoint) {
  console.log('closestHooters', closestHooters);

//placing the owl, these lines from here
  var markers = [];
google.maps.event.addListener(map, 'click', function(event) {
    addMarker(event.latLng);
  });
function addMarker(waypts) {
  var marker = new google.maps.Marker({
    position: waypts,
    map: map,
    image: 'http://edwinacevedo.com/directionsFrom/img/hooters-marker.png'
  });
  markers.push(marker);
}

//to here

  var whereTo = document.querySelector('#whereTo').value;
     var hooters2ndAve = new google.maps.LatLng(36.1618914, -86.789464);
  var waypts = [{
    location: hooters2ndAve, //this is undefined
    stopover: true
  }];
  directionsDisplay = new google.maps.DirectionsRenderer();
  directionsDisplay.setMap(map);
  navigator.geolocation.getCurrentPosition(function (position) {
    var pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
  var request = {
    origin: pos, //from geolocation
    destination: whereTo, //from input.value of #whereTo
    waypoints: waypts,
    //placeId: 'ChIJOwE7_GTtwokRFq0uOwLSE9g'
    optimizeWaypoints: false,
    travelMode: google.maps.TravelMode.DRIVING
  };
  console.log('waypts', waypts);
  console.log('map',  map);
  console.log('pos', pos);
  console.log('whereTo, this time including the input.value', whereTo);

  directionsService.route(request, function (response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      directionsDisplay.setDirections(response);
    }
  });
  });
}
google.maps.event.addDomListener(window, 'load', initialize, calcRoute, closestHooters);
<commit_msg>infowindow testing<commit_after>var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
var pos;
var destLatLng;
////////////////////////
//start the party
////////////////////////
function initialize() {
    var mapCanvas = document.getElementById('map-canvas'); //div in the html
    var mapOptions = {
      center: new google.maps.LatLng(36.1667, -86.7833), //NashVegas, TN
      zoom: 15,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      scrollwheel: false,
    };
    map = new google.maps.Map(mapCanvas, mapOptions);
    console.log('map', map); //this works, produces map object
    // Try HTML5 geolocation
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function (position) {
        pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
        var infowindow = new google.maps.InfoWindow({
          map: map,
          position: pos,
          pixelOffset: new google.maps.Size(0, -20),
          content: 'Start here'
        });
        console.log(pos);
        var marker = new google.maps.Marker({
          position: pos,
          map: map,
          animation: google.maps.Animation.DROP,
          title: 'This is your geolocation'
        });
        //copied the next six lines from google, not gonna lie
        google.maps.event.addListener(marker, 'click', toggleBounce);

        function toggleBounce() {
          if (marker.getAnimation() !== null) {
            marker.setAnimation(null);
          } else {
            marker.setAnimation(google.maps.Animation.BOUNCE);
          }
        }
        google.maps.event.addListener(marker, 'click', function () {
          infowindow.open(map, marker); //this should be redone in the infobox class
        });
        map.setCenter(pos);
        console.log('map inside the geolocation function', map);
        //refactored code from weather app
        var btnWhereTo = document.querySelector('#btnWhereTo'); //creates the button element using the id from the button input
        btnWhereTo.onclick = function () {
          closestHooters();
          calcRoute(pos);
        };
        //adds the calcroute within geolocation because geolocation is the starting point
      }, function () {
        handleNoGeolocation(true);
      });
    } else {
      // Browser doesn't support Geolocation
      handleNoGeolocation(false);
    }
  }
  //////////////////////////////
  //returns objects! hallelujah!
  //////////////////////////////
function closestHooters() {
  var whereTo = document.querySelector('#whereTo').value.split(' ').join('+');
  var MAPS_URL = 'https://maps.googleapis.com/maps/api/geocode/json?address=';
  $.get(MAPS_URL + whereTo, function (data) {
    console.log('data', data); //now it sees this
    var hootersDistances = [];
    var destLat = data.results[0].geometry.location.lat;
    var destLatCoord = parseFloat(destLat); //otherwise returns a string
    console.log("destination latitude with parstFloat", destLat);
    var destLng = data.results[0].geometry.location.lng;
    var destLngCoord = parseFloat(destLng);
    var destLatLng = new google.maps.LatLng(destLatCoord, destLngCoord);
    console.log("both together; are these numbers?", destLatLng);
    var hooters2ndAve = new google.maps.LatLng(36.1618914, -86.789464);
    var hootersLebanonPike = new google.maps.LatLng(36.18633370000001, -86.63314799999999);
    var hootersLargoDrive = new google.maps.LatLng(36.081514, -86.7095413);
    console.log("both together and the 2nd Ave Hooters", destLatLng, "2nd Ave", hooters2ndAve, "are these objects?");
    var distSecondAve = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hooters2ndAve);
    var distLebanonPike = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hootersLebanonPike);
    var distLargoDrive = google.maps.geometry.spherical.computeDistanceBetween(destLatLng, hootersLargoDrive);
    hootersDistances.push(distLargoDrive, distLebanonPike, distSecondAve);
    var nearestHooters = Math.min.apply(Math, hootersDistances);
    var hootersWaypoint = function () {
      if (nearestHooters === distSecondAve) {
        return hooters2ndAve;
      } else if (nearestHooters === distLargoDrive) {
        return hootersLargoDrive;
      } else if (nearestHooters === distLebanonPike) {
        return hootersLebanonPike;
      }
    };
    hootersWaypoint();
    var hootersMarker = new google.maps.Marker({
      position: hooters2ndAve,
      map: map,
      icon: 'http://edwinacevedo.com/directionsFrom/img/hooters-marker.png'
    });
  });
}

function handleNoGeolocation(errorFlag) {
    if (errorFlag) {
      var content = 'Aw snap! Something went wrong with the Geolocation service. Sorry?';
    } else {
      var content = 'Aw, snap! Your browser doesn\'t support geolocation.';
    }
    var options = {
      map: map,
      position: new google.maps.LatLng(36.1667, 86.7833),
      content: content
    };
    var infowindow = new google.maps.InfoWindow(options);
    map.setCenter(options.position);
  }
  //////////////////////////////////
  //autocomplete for the search box
  /////////////////////////////////
var pac_input = document.getElementById('whereTo');
(function pacSelectFirst(input) {
  // store the original event binding function
  var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent;

  function addEventListenerWrapper(type, listener) {
    // Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
    // and then trigger the original listener.
    if (type == "keydown") {
      var orig_listener = listener;
      listener = function (event) {
        var suggestion_selected = $(".pac-item-selected").length > 0;
        if (event.which == 13 && !suggestion_selected) {
          var simulated_downarrow = $.Event("keydown", {
            keyCode: 40,
            which: 40
          });
          orig_listener.apply(input, [simulated_downarrow]);
        }
        orig_listener.apply(input, [event]);
      };
    }
    // add the modified listener
    _addEventListener.apply(input, [type, listener]);
  }
  if (input.addEventListener)
    input.addEventListener = addEventListenerWrapper;
  else if (input.attachEvent)
    input.attachEvent = addEventListenerWrapper;
})(pac_input);
$(function () {
  var autocomplete = new google.maps.places.Autocomplete(pac_input);
});
//////////////////////////////
//draw the route on the map
/////////////////////////////
function calcRoute(pos, destLatLng, hootersWaypoint) {
  //console.log('closestHooters', closestHooters);
  //placing the owl, these lines from here
  var markers = [];
  google.maps.event.addListener(map, 'click', function (event) {
     addMarker(event.latLng);
   });
  //////////////////////////////////////
  //changing the name from marker to hootersMarker
  //////////////////////////////////////
  console.log(hootersWaypoint, 'hootersWaypoint')
  var hootersMarker = new google.maps.Marker({
    position: waypts,
    map: map,
    title: 'Stop for wings and beer!',
    icon: 'http://edwinacevedo.com/directionsFrom/img/hooters-marker.png'
  });
  markers.push(hootersMarker);
  // ### end owl marker
  //added infowindow from here
  /*var contentString = '
' +
    '
Stop here for beer & wings
' +
    '
';*/
  /*var infowindow = new google.maps.InfoWindow({
    content: 'this is a test ', //contentString,
    maxWidth: 300
  });*/
  /*  var hooters2ndAveMarker = new google.maps.Marker({
        position: hooters2ndAve,
        map: map,
        title: 'Stop for Beer and Wings'
    });*/
  google.maps.event.addListener(hootersMarker, 'click', function () {
    infowindow.open(map, hootersMarker);
    console.log('map object:', map, 'hooters marker:', hootersMarker);
    alert(map);
    alert(hootersMarker);
  });
  // #### end infowindow
  var whereTo = document.querySelector('#whereTo').value;
  var hooters2ndAve = new google.maps.LatLng(36.1618914, -86.789464);
  var waypts = [{
    location: hooters2ndAve, //this is undefined
    stopover: true
  }];
  directionsDisplay = new google.maps.DirectionsRenderer( {
     suppressMarkers : true
  });
  directionsDisplay.setMap(map);
  navigator.geolocation.getCurrentPosition(function (position) {
    var pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
    var request = {
      origin: pos,
      destination: whereTo, //from input.value of #whereTo
      waypoints: waypts,
      //placeId: 'ChIJOwE7_GTtwokRFq0uOwLSE9g'
      optimizeWaypoints: false,
      display: false,
      travelMode: google.maps.TravelMode.DRIVING
    };
    console.log('waypts', waypts);
    console.log('map', map);
    console.log('pos', pos);
    console.log('whereTo, this time including the input.value', whereTo);
    directionsService.route(request, function (response, status) {
      if (status == google.maps.DirectionsStatus.OK) {
        directionsDisplay.setDirections(response);
      }
    });
  });
}
google.maps.event.addDomListener(window, 'load', initialize, calcRoute/*, closestHooters*/); //, calcRoute, closestHooters
<|endoftext|>


<commit_before>const { expect } = require('chai');

const sort = require('../../src/sort');

describe('sort', () => {
  let flatArray;
  let persons;
  let multiPropArray;

  function assertOrder(order, valueCb) {
    order.forEach((item, idx) => {
      expect(valueCb(idx)).to.equal(item);
    });
  }

  beforeEach(() => {
    flatArray = [1, 5, 3, 2, 4, 5];

    persons = [{
      name: 'last',
      dob: new Date(1987, 14, 11),
      address: { code: 3 }
    }, {
      name: 'FIRST',
      dob: new Date(1987, 14, 9),
      address: {}
    }, {
      name: 'In the middle',
      dob: new Date(1987, 14, 10),
      address: { code: 1 }
    }];

    multiPropArray = [{
      name: 'aa',
      lastName: 'aa',
      age: 10
    }, {
      name: 'aa',
      lastName: undefined,
      age: 8
    }, {
      name: 'aa',
      lastName: null,
      age: 9
    }, {
      name: 'aa',
      lastName: 'bb',
      age: 11
    }, {
      name: 'bb',
      lastName: 'aa',
      age: 6
    }];
  });

  it('Should sort flat array in ascending order', () => {
    const sorted = sort(flatArray).asc();
    expect(sorted).to.eql([1, 2, 3, 4, 5, 5]);
    expect(sorted).to.equal(flatArray);
  });

  it('Should sort flat array in descending order', () => {
    expect(sort(flatArray).desc()).to.eql([5, 5, 4, 3, 2, 1]);
  });

  it('Should sort by object property in ascending order', () => {
    sort(persons).asc((p) => p.name.toLowerCase());
    assertOrder(['FIRST', 'In the middle', 'last'], (idx) => persons[idx].name);
  });

  it('Should sort by object property in descending order', () => {
    sort(persons).desc((p) => p.name.toLowerCase());
    assertOrder(['last', 'In the middle', 'FIRST'], (idx) => persons[idx].name);
  });

  it('Should sort undefined values to the bottom', () => {
    sort(persons).asc((p) => p.address.code);
    assertOrder([1, 3, undefined], (idx) => persons[idx].address.code);

    sort(persons).desc((p) => p.address.code);
    assertOrder([3, 1, undefined], (idx) => persons[idx].address.code);

    const sorted = sort([1, undefined, 3, null, 2]).desc();
    expect(sorted).to.eql([3, 2, 1, null, undefined]);
  });

  it('Should ignore values that are not sortable', () => {
    expect(sort('string').asc()).to.equal('string');
    expect(sort(undefined).desc()).to.equal(undefined);
    expect(sort(null).desc()).to.equal(null);
    expect(sort(33).asc()).to.equal(33);
    expect(sort({ name: 'test' }).desc()).to.eql({ name: 'test' });
    expect(sort(33).by()).to.equal(33);
  });

  it('Should sort dates correctly', () => {
    sort(persons).asc('dob');
    assertOrder(['FIRST', 'In the middle', 'last'], (idx) => persons[idx].name);
  });

  it('Should should unwrap single array value', () => {
    sort(persons).asc(['name']);
    assertOrder(['FIRST', 'In the middle', 'last'], (idx) => persons[idx].name);
  });

  it('Should sort on multiple properties', () => {
    sort(multiPropArray).asc([
      (p) => p.name,
      (p) => p.lastName,
      (p) => p.age
    ]);

    assertOrder([10, 11, 8, 9, 6], (idx) => multiPropArray[idx].age);
  });

  it('Should sort on multiple properties by string sorter', () => {
    sort(multiPropArray).asc(['name', 'age', 'lastName']);
    assertOrder([8, 9, 10, 11, 6], (idx) => multiPropArray[idx].age);
  });

  it('Should sort on multiple mixed properties', () => {
    sort(multiPropArray).asc(['name', (p) => p.lastName, 'age']);
    assertOrder([10, 11, 8, 9, 6], (idx) => multiPropArray[idx].age);
  });

  it('Should sort with all equal values', () => {
    const same = [{
      name: 'a',
      age: 1
    }, {
      name: 'a',
      age: 1
    }];

    sort(same).asc(['name', 'age']);
    expect(same[0].name).to.equal('a');
  });

  it('Should sort by desc name and asc lastName', () => {
    sort(multiPropArray).by([
      { desc: 'name' },
      { asc: 'lastName' }
    ]);

    assertOrder(['aa', 'aa', 'bb', undefined, null], (idx) => multiPropArray[idx].lastName);
  });

  it('Should sort by asc name and desc age', () => {
    sort(multiPropArray).by([
      { asc: 'name' },
      { desc: 'age' }
    ]);

    assertOrder([11, 10, 9, 8, 6], (idx) => multiPropArray[idx].age);
  });

  it('Should sort by asc lastName, desc name and asc age', () => {
    sort(multiPropArray).by([
      { asc: (p) => p.lastName },
      { desc: (p) => p.name },
      { asc: (p) => p.age }
    ]);

    assertOrder([6, 10, 11, 8, 9], (idx) => multiPropArray[idx].age);
  });

  it('Should throw invalid usage of by sorter exception', () => {
    expect(() =>
      sort(multiPropArray).by('name')
        .to.throw(Error));

    expect(() =>
      sort(multiPropArray).by([{ asci: 'name' }])
        .to.throw(Error));

    expect(() =>
      sort(multiPropArray).by([{ asc: 'lastName' }, { ass: 'name' }]))
      .to.throw(Error);
  });

  it('Should sort ascending with by on 1 property', () => {
    sort(multiPropArray).by([
      { asc: (p) => p.age }
    ]);

    assertOrder([6, 8, 9, 10, 11], (idx) => multiPropArray[idx].age);
  });

  it('Should sort descending with by on 1 property', () => {
    sort(multiPropArray).by([
      { desc: (p) => p.age }
    ]);

    assertOrder([11, 10, 9, 8, 6], (idx) => multiPropArray[idx].age);
  });
});
<commit_msg>Refactor sort test<commit_after>const { expect, assert } = require('chai');
const sort = require('../../src/sort');

describe('sort', () => {
  let flatArray;
  let persons;
  let multiPropArray;

  function assertOrder(order, valueCb) {
    order.forEach((item, idx) => {
      assert.equal(valueCb(idx), item);
    });
  }

  beforeEach(() => {
    flatArray = [1, 5, 3, 2, 4, 5];

    persons = [{
      name: 'last',
      dob: new Date(1987, 14, 11),
      address: { code: 3 }
    }, {
      name: 'FIRST',
      dob: new Date(1987, 14, 9),
      address: {}
    }, {
      name: 'In the middle',
      dob: new Date(1987, 14, 10),
      address: { code: 1 }
    }];

    multiPropArray = [{
      name: 'aa',
      lastName: 'aa',
      age: 10
    }, {
      name: 'aa',
      lastName: undefined,
      age: 8
    }, {
      name: 'aa',
      lastName: null,
      age: 9
    }, {
      name: 'aa',
      lastName: 'bb',
      age: 11
    }, {
      name: 'bb',
      lastName: 'aa',
      age: 6
    }];
  });

  it('Should sort flat array in ascending order', () => {
    const sorted = sort(flatArray).asc();
    assert.deepEqual(sorted, [1, 2, 3, 4, 5, 5]);
    assert.equal(sorted, flatArray);
  });

  it('Should sort flat array in descending order', () => {
    assert.deepEqual(sort(flatArray).desc(), [5, 5, 4, 3, 2, 1]);
  });

  it('Should sort by object property in ascending order', () => {
    sort(persons).asc(p => p.name.toLowerCase());
    assertOrder(['FIRST', 'In the middle', 'last'], idx => persons[idx].name);
  });

  it('Should sort by object property in descending order', () => {
    sort(persons).desc((p) => p.name.toLowerCase());
    assertOrder(['last', 'In the middle', 'FIRST'], idx => persons[idx].name);
  });

  it('Should sort undefined values to the bottom', () => {
    sort(persons).asc((p) => p.address.code);
    assertOrder([1, 3, undefined], idx => persons[idx].address.code);

    sort(persons).desc((p) => p.address.code);
    assertOrder([3, 1, undefined], idx => persons[idx].address.code);

    const sorted = sort([1, undefined, 3, null, 2]).desc();
    assert.deepEqual(sorted, [3, 2, 1, null, undefined]);
  });

  it('Should ignore values that are not sortable', () => {
    assert.equal(sort('string').asc(), 'string');
    assert.equal(sort(undefined).desc(), undefined);
    assert.equal(sort(null).desc(), null);
    assert.equal(sort(33).asc(), 33);
    assert.deepEqual(sort({ name: 'test' }).desc(), { name: 'test' });
    assert.equal(sort(33).by(), 33);
  });

  it('Should sort dates correctly', () => {
    sort(persons).asc('dob');
    assertOrder(['FIRST', 'In the middle', 'last'], idx => persons[idx].name);
  });

  it('Should should unwrap single array value', () => {
    sort(persons).asc(['name']);
    assertOrder(['FIRST', 'In the middle', 'last'], idx => persons[idx].name);
  });

  it('Should sort on multiple properties', () => {
    sort(multiPropArray).asc([
      p => p.name,
      p => p.lastName,
      p => p.age
    ]);

    assertOrder([10, 11, 8, 9, 6], idx => multiPropArray[idx].age);
  });

  it('Should sort on multiple properties by string sorter', () => {
    sort(multiPropArray).asc(['name', 'age', 'lastName']);
    assertOrder([8, 9, 10, 11, 6], idx => multiPropArray[idx].age);
  });

  it('Should sort on multiple mixed properties', () => {
    sort(multiPropArray).asc(['name', p => p.lastName, 'age']);
    assertOrder([10, 11, 8, 9, 6], idx => multiPropArray[idx].age);
  });

  it('Should sort with all equal values', () => {
    const same = [{
      name: 'a',
      age: 1
    }, {
      name: 'a',
      age: 1
    }];

    sort(same).asc(['name', 'age']);
    assert.equal(same[0].name, 'a');
  });

  it('Should sort by desc name and asc lastName', () => {
    sort(multiPropArray).by([
      { desc: 'name' },
      { asc: 'lastName' }
    ]);

    assertOrder(['aa', 'aa', 'bb', undefined, null], idx => multiPropArray[idx].lastName);
  });

  it('Should sort by asc name and desc age', () => {
    sort(multiPropArray).by([
      { asc: 'name' },
      { desc: 'age' }
    ]);

    assertOrder([11, 10, 9, 8, 6], idx => multiPropArray[idx].age);
  });

  it('Should sort by asc lastName, desc name and asc age', () => {
    sort(multiPropArray).by([
      { asc: p => p.lastName },
      { desc: p => p.name },
      { asc: p => p.age }
    ]);

    assertOrder([6, 10, 11, 8, 9], idx => multiPropArray[idx].age);
  });

  it('Should throw invalid usage of by sorter exception', () => {
    expect(() => sort(multiPropArray).by('name'))
      .to.throw(Error);

    expect(() => sort(multiPropArray).by([{ asci: 'name' }]))
      .to.throw(Error);

    expect(() => sort(multiPropArray).by([{ asc: 'lastName' }, { ass: 'name' }]))
      .to.throw(Error);
  });

  it('Should sort ascending with by on 1 property', () => {
    sort(multiPropArray).by([
      { asc: p => p.age }
    ]);

    assertOrder(
      [6, 8, 9, 10, 11],
      idx => multiPropArray[idx].age
    );
  });

  it('Should sort descending with by on 1 property', () => {
    sort(multiPropArray).by([
      { desc: p => p.age }
    ]);

    assertOrder(
      [11, 10, 9, 8, 6],
      idx => multiPropArray[idx].age,
    );
  });
});
<|endoftext|>


<commit_before>module.exports = {
  peerStar: {
    ipfs: {
      // swarm: ['/dns4/ws-star2.sjc.dwebops.pub/tcp/443/wss/p2p-websocket-star']
      // swarm: ['/dns4/ws-star1.par.dwebops.pub/tcp/443/wss/p2p-websocket-star']
      swarm: ['/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star']
    },
    transport: {
      maxThrottleDelayMS: 0
    }
  }
}
<commit_msg>chore: config update<commit_after>module.exports = {
  peerStar: {
    ipfs: {
      swarm: ['/dns4/ws-star2.sjc.dwebops.pub/tcp/443/wss/p2p-websocket-star']
      // swarm: ['/dns4/ws-star1.par.dwebops.pub/tcp/443/wss/p2p-websocket-star']
      // swarm: ['/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star']
    },
    transport: {
      maxThrottleDelayMS: 1000
    }
  }
}
<|endoftext|>


<commit_before>/* Copyright 2017 Mozilla Foundation
 *
 * 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.
 */

import {
  bytesToString,
  createPromiseCapability,
  createValidAbsoluteUrl,
  escapeString,
  getModificationDate,
  isArrayBuffer,
  isAscii,
  isBool,
  isNum,
  isSameOrigin,
  isString,
  removeNullCharacters,
  string32,
  stringToBytes,
  stringToPDFString,
  stringToUTF16BEString,
} from "../../src/shared/util.js";

describe("util", function () {
  describe("bytesToString", function () {
    it("handles non-array arguments", function () {
      expect(function () {
        bytesToString(null);
      }).toThrow(new Error("Invalid argument for bytesToString"));
    });

    it("handles array arguments with a length not exceeding the maximum", function () {
      expect(bytesToString(new Uint8Array([]))).toEqual("");
      expect(bytesToString(new Uint8Array([102, 111, 111]))).toEqual("foo");
    });

    it("handles array arguments with a length exceeding the maximum", function () {
      const length = 10000; // Larger than MAX_ARGUMENT_COUNT = 8192.

      // Create an array with `length` 'a' character codes.
      const bytes = new Uint8Array(length);
      for (let i = 0; i < length; i++) {
        bytes[i] = "a".charCodeAt(0);
      }

      // Create a string with `length` 'a' characters. We need an array of size
      // `length + 1` since `join` puts the argument between the array elements.
      const string = Array(length + 1).join("a");

      expect(bytesToString(bytes)).toEqual(string);
    });
  });

  describe("isArrayBuffer", function () {
    it("handles array buffer values", function () {
      expect(isArrayBuffer(new ArrayBuffer(0))).toEqual(true);
      expect(isArrayBuffer(new Uint8Array(0))).toEqual(true);
    });

    it("handles non-array buffer values", function () {
      expect(isArrayBuffer("true")).toEqual(false);
      expect(isArrayBuffer(1)).toEqual(false);
      expect(isArrayBuffer(null)).toEqual(false);
      expect(isArrayBuffer(undefined)).toEqual(false);
    });
  });

  describe("isBool", function () {
    it("handles boolean values", function () {
      expect(isBool(true)).toEqual(true);
      expect(isBool(false)).toEqual(true);
    });

    it("handles non-boolean values", function () {
      expect(isBool("true")).toEqual(false);
      expect(isBool("false")).toEqual(false);
      expect(isBool(1)).toEqual(false);
      expect(isBool(0)).toEqual(false);
      expect(isBool(null)).toEqual(false);
      expect(isBool(undefined)).toEqual(false);
    });
  });

  describe("isNum", function () {
    it("handles numeric values", function () {
      expect(isNum(1)).toEqual(true);
      expect(isNum(0)).toEqual(true);
      expect(isNum(-1)).toEqual(true);
      expect(isNum(1000000000000000000)).toEqual(true);
      expect(isNum(12.34)).toEqual(true);
    });

    it("handles non-numeric values", function () {
      expect(isNum("true")).toEqual(false);
      expect(isNum(true)).toEqual(false);
      expect(isNum(null)).toEqual(false);
      expect(isNum(undefined)).toEqual(false);
    });
  });

  describe("isString", function () {
    it("handles string values", function () {
      expect(isString("foo")).toEqual(true);
      expect(isString("")).toEqual(true);
    });

    it("handles non-string values", function () {
      expect(isString(true)).toEqual(false);
      expect(isString(1)).toEqual(false);
      expect(isString(null)).toEqual(false);
      expect(isString(undefined)).toEqual(false);
    });
  });

  describe("string32", function () {
    it("converts unsigned 32-bit integers to strings", function () {
      expect(string32(0x74727565)).toEqual("true");
      expect(string32(0x74797031)).toEqual("typ1");
      expect(string32(0x4f54544f)).toEqual("OTTO");
    });
  });

  describe("stringToBytes", function () {
    it("handles non-string arguments", function () {
      expect(function () {
        stringToBytes(null);
      }).toThrow(new Error("Invalid argument for stringToBytes"));
    });

    it("handles string arguments", function () {
      expect(stringToBytes("")).toEqual(new Uint8Array([]));
      expect(stringToBytes("foo")).toEqual(new Uint8Array([102, 111, 111]));
    });
  });

  describe("stringToPDFString", function () {
    it("handles ISO Latin 1 strings", function () {
      const str = "\x8Dstring\x8E";
      expect(stringToPDFString(str)).toEqual("\u201Cstring\u201D");
    });

    it("handles UTF-16 big-endian strings", function () {
      const str = "\xFE\xFF\x00\x73\x00\x74\x00\x72\x00\x69\x00\x6E\x00\x67";
      expect(stringToPDFString(str)).toEqual("string");
    });

    it("handles UTF-16 little-endian strings", function () {
      const str = "\xFF\xFE\x73\x00\x74\x00\x72\x00\x69\x00\x6E\x00\x67\x00";
      expect(stringToPDFString(str)).toEqual("string");
    });

    it("handles empty strings", function () {
      // ISO Latin 1
      const str1 = "";
      expect(stringToPDFString(str1)).toEqual("");

      // UTF-16BE
      const str2 = "\xFE\xFF";
      expect(stringToPDFString(str2)).toEqual("");

      // UTF-16LE
      const str3 = "\xFF\xFE";
      expect(stringToPDFString(str3)).toEqual("");
    });
  });

  describe("removeNullCharacters", function () {
    it("should not modify string without null characters", function () {
      const str = "string without null chars";
      expect(removeNullCharacters(str)).toEqual("string without null chars");
    });

    it("should modify string with null characters", function () {
      const str = "string\x00With\x00Null\x00Chars";
      expect(removeNullCharacters(str)).toEqual("stringWithNullChars");
    });
  });

  describe("ReadableStream", function () {
    it("should return an Object", function () {
      const readable = new ReadableStream();
      expect(typeof readable).toEqual("object");
    });

    it("should have property getReader", function () {
      const readable = new ReadableStream();
      expect(typeof readable.getReader).toEqual("function");
    });
  });

  describe("URL", function () {
    it("should return an Object", function () {
      const url = new URL("https://example.com");
      expect(typeof url).toEqual("object");
    });

    it("should have property `href`", function () {
      const url = new URL("https://example.com");
      expect(typeof url.href).toEqual("string");
    });
  });

  describe("isSameOrigin", function () {
    it("handles invalid base URLs", function () {
      // The base URL is not valid.
      expect(isSameOrigin("/foo", "/bar")).toEqual(false);

      // The base URL has no origin.
      expect(isSameOrigin("blob:foo", "/bar")).toEqual(false);
    });

    it("correctly checks if the origin of both URLs matches", function () {
      expect(
        isSameOrigin(
          "https://www.mozilla.org/foo",
          "https://www.mozilla.org/bar"
        )
      ).toEqual(true);
      expect(
        isSameOrigin(
          "https://www.mozilla.org/foo",
          "https://www.example.com/bar"
        )
      ).toEqual(false);
    });
  });

  describe("createValidAbsoluteUrl", function () {
    it("handles invalid URLs", function () {
      expect(createValidAbsoluteUrl(undefined, undefined)).toEqual(null);
      expect(createValidAbsoluteUrl(null, null)).toEqual(null);
      expect(createValidAbsoluteUrl("/foo", "/bar")).toEqual(null);
    });

    it("handles URLs that do not use an allowed protocol", function () {
      expect(createValidAbsoluteUrl("magnet:?foo", null)).toEqual(null);
    });

    it("correctly creates a valid URL for allowed protocols", function () {
      // `http` protocol
      expect(
        createValidAbsoluteUrl("http://www.mozilla.org/foo", null)
      ).toEqual(new URL("http://www.mozilla.org/foo"));
      expect(createValidAbsoluteUrl("/foo", "http://www.mozilla.org")).toEqual(
        new URL("http://www.mozilla.org/foo")
      );

      // `https` protocol
      expect(
        createValidAbsoluteUrl("https://www.mozilla.org/foo", null)
      ).toEqual(new URL("https://www.mozilla.org/foo"));
      expect(createValidAbsoluteUrl("/foo", "https://www.mozilla.org")).toEqual(
        new URL("https://www.mozilla.org/foo")
      );

      // `ftp` protocol
      expect(createValidAbsoluteUrl("ftp://www.mozilla.org/foo", null)).toEqual(
        new URL("ftp://www.mozilla.org/foo")
      );
      expect(createValidAbsoluteUrl("/foo", "ftp://www.mozilla.org")).toEqual(
        new URL("ftp://www.mozilla.org/foo")
      );

      // `mailto` protocol (base URLs have no meaning and should yield `null`)
      expect(createValidAbsoluteUrl("mailto:foo@bar.baz", null)).toEqual(
        new URL("mailto:foo@bar.baz")
      );
      expect(createValidAbsoluteUrl("/foo", "mailto:foo@bar.baz")).toEqual(
        null
      );

      // `tel` protocol (base URLs have no meaning and should yield `null`)
      expect(createValidAbsoluteUrl("tel:+0123456789", null)).toEqual(
        new URL("tel:+0123456789")
      );
      expect(createValidAbsoluteUrl("/foo", "tel:0123456789")).toEqual(null);
    });
  });

  describe("createPromiseCapability", function () {
    it("should resolve with correct data", function (done) {
      const promiseCapability = createPromiseCapability();
      expect(promiseCapability.settled).toEqual(false);

      promiseCapability.resolve({ test: "abc" });

      promiseCapability.promise.then(function (data) {
        expect(promiseCapability.settled).toEqual(true);

        expect(data).toEqual({ test: "abc" });
        done();
      }, done.fail);
    });

    it("should reject with correct reason", function (done) {
      const promiseCapability = createPromiseCapability();
      expect(promiseCapability.settled).toEqual(false);

      promiseCapability.reject(new Error("reason"));

      promiseCapability.promise.then(done.fail, function (reason) {
        expect(promiseCapability.settled).toEqual(true);

        expect(reason instanceof Error).toEqual(true);
        expect(reason.message).toEqual("reason");
        done();
      });
    });
  });

  describe("escapeString", function () {
    it("should escape (, ), \\n, \\r, and \\", function () {
      expect(escapeString("((a\\a))\n(b(b\\b)\rb)")).toEqual(
        "\\(\\(a\\\\a\\)\\)\\n\\(b\\(b\\\\b\\)\\rb\\)"
      );
    });
  });

  describe("getModificationDate", function () {
    it("should get a correctly formatted date", function () {
      const date = new Date(Date.UTC(3141, 5, 9, 2, 6, 53));
      expect(getModificationDate(date)).toEqual("31410609020653");
    });
  });

  describe("isAscii", function () {
    it("handles ascii/non-ascii strings", function () {
      expect(isAscii("hello world")).toEqual(true);
      expect(isAscii("こんにちは世界の")).toEqual(false);
      expect(isAscii("hello world in Japanese is こんにちは世界の")).toEqual(
        false
      );
    });
  });

  describe("stringToUTF16BEString", function () {
    it("should encode a string in UTF16BE with a BOM", function () {
      expect(stringToUTF16BEString("hello world")).toEqual(
        "\xfe\xff\0h\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d"
      );
      expect(stringToUTF16BEString("こんにちは世界の")).toEqual(
        "\xfe\xff\x30\x53\x30\x93\x30\x6b\x30\x61" +
          "\x30\x6f\x4e\x16\x75\x4c\x30\x6e"
      );
    });
  });
});
<commit_msg>Convert done callbacks to async/await in `test/unit/util_spec.js`<commit_after>/* Copyright 2017 Mozilla Foundation
 *
 * 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.
 */

import {
  bytesToString,
  createPromiseCapability,
  createValidAbsoluteUrl,
  escapeString,
  getModificationDate,
  isArrayBuffer,
  isAscii,
  isBool,
  isNum,
  isSameOrigin,
  isString,
  removeNullCharacters,
  string32,
  stringToBytes,
  stringToPDFString,
  stringToUTF16BEString,
} from "../../src/shared/util.js";

describe("util", function () {
  describe("bytesToString", function () {
    it("handles non-array arguments", function () {
      expect(function () {
        bytesToString(null);
      }).toThrow(new Error("Invalid argument for bytesToString"));
    });

    it("handles array arguments with a length not exceeding the maximum", function () {
      expect(bytesToString(new Uint8Array([]))).toEqual("");
      expect(bytesToString(new Uint8Array([102, 111, 111]))).toEqual("foo");
    });

    it("handles array arguments with a length exceeding the maximum", function () {
      const length = 10000; // Larger than MAX_ARGUMENT_COUNT = 8192.

      // Create an array with `length` 'a' character codes.
      const bytes = new Uint8Array(length);
      for (let i = 0; i < length; i++) {
        bytes[i] = "a".charCodeAt(0);
      }

      // Create a string with `length` 'a' characters. We need an array of size
      // `length + 1` since `join` puts the argument between the array elements.
      const string = Array(length + 1).join("a");

      expect(bytesToString(bytes)).toEqual(string);
    });
  });

  describe("isArrayBuffer", function () {
    it("handles array buffer values", function () {
      expect(isArrayBuffer(new ArrayBuffer(0))).toEqual(true);
      expect(isArrayBuffer(new Uint8Array(0))).toEqual(true);
    });

    it("handles non-array buffer values", function () {
      expect(isArrayBuffer("true")).toEqual(false);
      expect(isArrayBuffer(1)).toEqual(false);
      expect(isArrayBuffer(null)).toEqual(false);
      expect(isArrayBuffer(undefined)).toEqual(false);
    });
  });

  describe("isBool", function () {
    it("handles boolean values", function () {
      expect(isBool(true)).toEqual(true);
      expect(isBool(false)).toEqual(true);
    });

    it("handles non-boolean values", function () {
      expect(isBool("true")).toEqual(false);
      expect(isBool("false")).toEqual(false);
      expect(isBool(1)).toEqual(false);
      expect(isBool(0)).toEqual(false);
      expect(isBool(null)).toEqual(false);
      expect(isBool(undefined)).toEqual(false);
    });
  });

  describe("isNum", function () {
    it("handles numeric values", function () {
      expect(isNum(1)).toEqual(true);
      expect(isNum(0)).toEqual(true);
      expect(isNum(-1)).toEqual(true);
      expect(isNum(1000000000000000000)).toEqual(true);
      expect(isNum(12.34)).toEqual(true);
    });

    it("handles non-numeric values", function () {
      expect(isNum("true")).toEqual(false);
      expect(isNum(true)).toEqual(false);
      expect(isNum(null)).toEqual(false);
      expect(isNum(undefined)).toEqual(false);
    });
  });

  describe("isString", function () {
    it("handles string values", function () {
      expect(isString("foo")).toEqual(true);
      expect(isString("")).toEqual(true);
    });

    it("handles non-string values", function () {
      expect(isString(true)).toEqual(false);
      expect(isString(1)).toEqual(false);
      expect(isString(null)).toEqual(false);
      expect(isString(undefined)).toEqual(false);
    });
  });

  describe("string32", function () {
    it("converts unsigned 32-bit integers to strings", function () {
      expect(string32(0x74727565)).toEqual("true");
      expect(string32(0x74797031)).toEqual("typ1");
      expect(string32(0x4f54544f)).toEqual("OTTO");
    });
  });

  describe("stringToBytes", function () {
    it("handles non-string arguments", function () {
      expect(function () {
        stringToBytes(null);
      }).toThrow(new Error("Invalid argument for stringToBytes"));
    });

    it("handles string arguments", function () {
      expect(stringToBytes("")).toEqual(new Uint8Array([]));
      expect(stringToBytes("foo")).toEqual(new Uint8Array([102, 111, 111]));
    });
  });

  describe("stringToPDFString", function () {
    it("handles ISO Latin 1 strings", function () {
      const str = "\x8Dstring\x8E";
      expect(stringToPDFString(str)).toEqual("\u201Cstring\u201D");
    });

    it("handles UTF-16 big-endian strings", function () {
      const str = "\xFE\xFF\x00\x73\x00\x74\x00\x72\x00\x69\x00\x6E\x00\x67";
      expect(stringToPDFString(str)).toEqual("string");
    });

    it("handles UTF-16 little-endian strings", function () {
      const str = "\xFF\xFE\x73\x00\x74\x00\x72\x00\x69\x00\x6E\x00\x67\x00";
      expect(stringToPDFString(str)).toEqual("string");
    });

    it("handles empty strings", function () {
      // ISO Latin 1
      const str1 = "";
      expect(stringToPDFString(str1)).toEqual("");

      // UTF-16BE
      const str2 = "\xFE\xFF";
      expect(stringToPDFString(str2)).toEqual("");

      // UTF-16LE
      const str3 = "\xFF\xFE";
      expect(stringToPDFString(str3)).toEqual("");
    });
  });

  describe("removeNullCharacters", function () {
    it("should not modify string without null characters", function () {
      const str = "string without null chars";
      expect(removeNullCharacters(str)).toEqual("string without null chars");
    });

    it("should modify string with null characters", function () {
      const str = "string\x00With\x00Null\x00Chars";
      expect(removeNullCharacters(str)).toEqual("stringWithNullChars");
    });
  });

  describe("ReadableStream", function () {
    it("should return an Object", function () {
      const readable = new ReadableStream();
      expect(typeof readable).toEqual("object");
    });

    it("should have property getReader", function () {
      const readable = new ReadableStream();
      expect(typeof readable.getReader).toEqual("function");
    });
  });

  describe("URL", function () {
    it("should return an Object", function () {
      const url = new URL("https://example.com");
      expect(typeof url).toEqual("object");
    });

    it("should have property `href`", function () {
      const url = new URL("https://example.com");
      expect(typeof url.href).toEqual("string");
    });
  });

  describe("isSameOrigin", function () {
    it("handles invalid base URLs", function () {
      // The base URL is not valid.
      expect(isSameOrigin("/foo", "/bar")).toEqual(false);

      // The base URL has no origin.
      expect(isSameOrigin("blob:foo", "/bar")).toEqual(false);
    });

    it("correctly checks if the origin of both URLs matches", function () {
      expect(
        isSameOrigin(
          "https://www.mozilla.org/foo",
          "https://www.mozilla.org/bar"
        )
      ).toEqual(true);
      expect(
        isSameOrigin(
          "https://www.mozilla.org/foo",
          "https://www.example.com/bar"
        )
      ).toEqual(false);
    });
  });

  describe("createValidAbsoluteUrl", function () {
    it("handles invalid URLs", function () {
      expect(createValidAbsoluteUrl(undefined, undefined)).toEqual(null);
      expect(createValidAbsoluteUrl(null, null)).toEqual(null);
      expect(createValidAbsoluteUrl("/foo", "/bar")).toEqual(null);
    });

    it("handles URLs that do not use an allowed protocol", function () {
      expect(createValidAbsoluteUrl("magnet:?foo", null)).toEqual(null);
    });

    it("correctly creates a valid URL for allowed protocols", function () {
      // `http` protocol
      expect(
        createValidAbsoluteUrl("http://www.mozilla.org/foo", null)
      ).toEqual(new URL("http://www.mozilla.org/foo"));
      expect(createValidAbsoluteUrl("/foo", "http://www.mozilla.org")).toEqual(
        new URL("http://www.mozilla.org/foo")
      );

      // `https` protocol
      expect(
        createValidAbsoluteUrl("https://www.mozilla.org/foo", null)
      ).toEqual(new URL("https://www.mozilla.org/foo"));
      expect(createValidAbsoluteUrl("/foo", "https://www.mozilla.org")).toEqual(
        new URL("https://www.mozilla.org/foo")
      );

      // `ftp` protocol
      expect(createValidAbsoluteUrl("ftp://www.mozilla.org/foo", null)).toEqual(
        new URL("ftp://www.mozilla.org/foo")
      );
      expect(createValidAbsoluteUrl("/foo", "ftp://www.mozilla.org")).toEqual(
        new URL("ftp://www.mozilla.org/foo")
      );

      // `mailto` protocol (base URLs have no meaning and should yield `null`)
      expect(createValidAbsoluteUrl("mailto:foo@bar.baz", null)).toEqual(
        new URL("mailto:foo@bar.baz")
      );
      expect(createValidAbsoluteUrl("/foo", "mailto:foo@bar.baz")).toEqual(
        null
      );

      // `tel` protocol (base URLs have no meaning and should yield `null`)
      expect(createValidAbsoluteUrl("tel:+0123456789", null)).toEqual(
        new URL("tel:+0123456789")
      );
      expect(createValidAbsoluteUrl("/foo", "tel:0123456789")).toEqual(null);
    });
  });

  describe("createPromiseCapability", function () {
    it("should resolve with correct data", async function () {
      const promiseCapability = createPromiseCapability();
      expect(promiseCapability.settled).toEqual(false);

      promiseCapability.resolve({ test: "abc" });

      const data = await promiseCapability.promise;
      expect(promiseCapability.settled).toEqual(true);
      expect(data).toEqual({ test: "abc" });
    });

    it("should reject with correct reason", async function () {
      const promiseCapability = createPromiseCapability();
      expect(promiseCapability.settled).toEqual(false);

      promiseCapability.reject(new Error("reason"));

      try {
        await promiseCapability.promise;

        // Shouldn't get here.
        expect(false).toEqual(true);
      } catch (reason) {
        expect(promiseCapability.settled).toEqual(true);
        expect(reason instanceof Error).toEqual(true);
        expect(reason.message).toEqual("reason");
      }
    });
  });

  describe("escapeString", function () {
    it("should escape (, ), \\n, \\r, and \\", function () {
      expect(escapeString("((a\\a))\n(b(b\\b)\rb)")).toEqual(
        "\\(\\(a\\\\a\\)\\)\\n\\(b\\(b\\\\b\\)\\rb\\)"
      );
    });
  });

  describe("getModificationDate", function () {
    it("should get a correctly formatted date", function () {
      const date = new Date(Date.UTC(3141, 5, 9, 2, 6, 53));
      expect(getModificationDate(date)).toEqual("31410609020653");
    });
  });

  describe("isAscii", function () {
    it("handles ascii/non-ascii strings", function () {
      expect(isAscii("hello world")).toEqual(true);
      expect(isAscii("こんにちは世界の")).toEqual(false);
      expect(isAscii("hello world in Japanese is こんにちは世界の")).toEqual(
        false
      );
    });
  });

  describe("stringToUTF16BEString", function () {
    it("should encode a string in UTF16BE with a BOM", function () {
      expect(stringToUTF16BEString("hello world")).toEqual(
        "\xfe\xff\0h\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d"
      );
      expect(stringToUTF16BEString("こんにちは世界の")).toEqual(
        "\xfe\xff\x30\x53\x30\x93\x30\x6b\x30\x61" +
          "\x30\x6f\x4e\x16\x75\x4c\x30\x6e"
      );
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013 Marshall Farrier
 * http://www.opensource.org/licenses/mit-license.php
 * Cf. Core HTML5 Canvas, pp. 395ff.
 *
 * Dependencies:
 * extend.js
 */
/** @namespace */
var _c = _c || {};

(function(_c) {
    "use strict";

    /** @namespace */
    _c.draw = _c.draw || {};
    /**
     * Creates a new _c.draw.Sprite
     * A sprite is a thin wrapper around a drawable to facilitate control of the
     * manner in which updates occur, namely by invoking various behavior functions
     * in sequence. This allows combining behaviors such as vertical and
     * horizontal motion or advancing the frame with appropriate drawables.
     * This implementation leaves out several parameters included in
     * David Geary's Core HTML5 Canvas:
     * box, velocity, visible, animating
     * These properties can be attached to specific sprites as needed
     * but just create extra baggage that is usually empty.
     * @constructor
     * @member {_c.draw.Drawable} drawable
     * @member {function} paint - paints the drawable into a context
     * @member {function[]} behaviors - methods that will be invoked when
     * this.update() is called
     * @member {function} update - invoke all behaviors, using the calling sprite as
     * this argument
     * */
    _c.draw.Sprite = _c.Base.extend({

        /**
         * @constructs _c.draw.Sprite
         * @param {object} params
         * @param {_c.draw.Drawable} params.drawable
         * @param {function} [params.paint] - paint method if different from Drawable#draw(context)
         * @default this.drawable.draw
         * @param {function[]} [params.behaviors] - methods that will be invoked when
         * this.update() is called.
         */
        init: function(params) {
            var _this = this;

            this.drawable = params.drawable;
            /**
             * For shapes Shape#draw() or Shape#drawClipped()
             * There is no need to create an object for this, as in the book.
             */
            this.paint = params.paint || function(context) {
                _this.drawable.draw(context);
            };
            // behaviors are functions, not objects, so no need for separate behavior.execute()
            this.behaviors = params.behaviors || [];
        },

        update: function(context, time) {
            var _this = this;

            this.behaviors.forEach(function(behavior) {
                // allows natural use of 'this' inside behavior function
                // a behavior thus only takes context and time as arguments
                behavior.call(_this, context, time);
            });
            return this;
        }
    });
})(_c);
<commit_msg>Documentation improvements.<commit_after>/**
 * Copyright (c) 2013 Marshall Farrier
 * http://www.opensource.org/licenses/mit-license.php
 * Cf. Core HTML5 Canvas, pp. 395ff.
 *
 * Dependencies:
 * extend.js
 */
/** @namespace */
var _c = _c || {};

(function(_c) {
    "use strict";

    /** @namespace */
    _c.draw = _c.draw || {};
    /**
     * Creates a new _c.draw.Sprite
     * A sprite is a thin wrapper around a drawable to facilitate control of the
     * manner in which updates occur, namely by invoking various behavior functions
     * in sequence. This allows combining behaviors such as vertical and
     * horizontal motion or advancing the frame with appropriate drawables.
     * This implementation leaves out several parameters included in
     * David Geary's Core HTML5 Canvas:
     * box, velocity, visible, animating
     * These properties can be attached to specific sprites as needed
     * but just create extra baggage that is usually empty.
     * @constructor
     * @member {_c.draw.Drawable} drawable
     * @member {function} paint - paints the drawable into a context
     * @member {function[]} behaviors - methods that will be invoked when
     * this.update() is called
     * @member {function} update - invoke all behaviors, using the calling sprite as
     * this argument
     */
    /**
     * @method paint
     * @param context - context into which to paint the drawable
     * @example
     * sprite.paint(context);
     */
    _c.draw.Sprite = _c.Base.extend({

        /**
         * @constructs _c.draw.Sprite
         * @param {object} params
         * @param {_c.draw.Drawable} params.drawable
         * @param {function} [params.paint] - paint method if different from Drawable#draw(context)
         * @default this.drawable.draw
         * @param {function[]} [params.behaviors] - methods that will be invoked when
         * this.update() is called.
         */
        /**
         * @function params.paint
         * @param context
         * @example
         * paint: function(context) {
         *     _this.drawable.stroke(context);
         * }
         */
        init: function(params) {
            var _this = this;

            this.drawable = params.drawable;
            /**
             * For shapes Shape#draw() or Shape#drawClipped()
             * There is no need to create an object for this, as in the book.
             */
            this.paint = params.paint || function(context) {
                _this.drawable.draw(context);
            };
            // behaviors are functions, not objects, so no need for separate behavior.execute()
            this.behaviors = params.behaviors || [];
        },

        update: function(context, time) {
            var _this = this;

            this.behaviors.forEach(function(behavior) {
                // allows natural use of 'this' inside behavior function
                // a behavior thus only takes context and time as arguments
                behavior.call(_this, context, time);
            });
            return this;
        }
    });
})(_c);
<|endoftext|>


<commit_before>import test from 'ava'
import Vue from 'vue'
import Vuet from '../../dist/vuet'

Vue.use(Vuet)

const myModule = 'myModule'
const listPath = `${myModule}/route/list`
const detailPath = `${myModule}/route/detail`
const newVuet = () => {
  return new Vuet({
    data () {
      return {
        laoding: true,
        loaded: true
      }
    },
    modules: {
      [myModule]: {
        route: {
          list: {
            data () {
              return {
                list: []
              }
            },
            fetch () {
              return Promise.resolve({ list: [1, 0] })
            }
          },
          detail: {
            data () {
              return {
                detail: {
                  id: null,
                  name: '',
                  age: 0,
                  sex: ''
                }
              }
            },
            fetch () {
              return Promise.reject(new Error('Error msg'))
            }
          }
        }
      }
    }
  })
}

test('base', async t => {
  const vuet = newVuet()
  const vm = new Vue({
    vuet
  })
  const listState = {
    laoding: true,
    loaded: true,
    list: []
  }
  const detailState = {
    laoding: true,
    loaded: true,
    detail: {
      id: null,
      name: '',
      age: 0,
      sex: ''
    }
  }
  t.deepEqual(vuet.store, {
    [`${myModule}/route/list`]: listState,
    [`${myModule}/route/detail`]: detailState
  })
  t.deepEqual(vuet.getState(listPath), listState)
  t.deepEqual(vuet.getState(detailPath), detailState)
  t.deepEqual(Object.keys(vuet._options.modules), Object.keys(vuet.store))
  t.is(vuet, vm.$options.vuet)
  t.is(vuet, vm._vuet)
  t.is(vuet, vm.$vuet)

  vuet.setState(listPath, { list: [0, 1] })
  t.deepEqual(vuet.getState(listPath).list, [0, 1])

  vuet.reset(listPath)
  t.deepEqual(vuet.getState(listPath), listState)

  const store = await vuet.fetch(listPath)
  t.deepEqual(store.list, [1, 0])
  t.deepEqual(vuet.getState(listPath).list, [1, 0])

  let beforeCount = 0
  let afterCount = 0
  let path = listPath
  vuet.beforeEach((params) => {
    t.is(params.path, path)
    t.is(params.store, vuet.getState(path))
    t.is(params.store, vuet.getState(params.path))
    t.deepEqual(params.params, {})
    beforeCount++
    store.laoding = true
    store.loaded = true
  })
  vuet.afterEach((err, params) => {
    t.is(params.path, path)
    t.is(params.store, vuet.getState(path))
    t.is(params.store, vuet.getState(params.path))
    t.deepEqual(params.params, {})
    store.loading = false
    store.loaded = (err === null)
    afterCount++
  })
  t.is(beforeCount, 0)
  t.is(afterCount, 0)
  await vuet.fetch(listPath)
  t.is(beforeCount, 1)
  t.is(afterCount, 1)
  t.false(vuet.getState(listPath).loading)
  t.true(vuet.getState(listPath).loaded)

  path = detailPath
  try {
    await vuet.fetch(detailPath)
  } catch (e) {
    t.is(String(e), 'Error: Error msg')
    t.is(beforeCount, 2)
    t.is(afterCount, 2)
    t.false(vuet.getState(listPath).loading)
    t.false(vuet.getState(listPath).loaded)
  }
})

test('use plugins', t => {
  const vuet = newVuet()
  const arr = ['life', 'local', 'need', 'once', 'route']
  t.deepEqual(Object.keys(Vuet.plugins), arr)
  arr.forEach(name => {
    t.is(Vuet.plugins[name].name, name)
  })

  let useCount = 0
  let initCount = 0
  let destroyCount = 0
  Vuet.use({
    name: 'testPlugin',
    install (_Vue, _Vuet, opt) {
      useCount++
      t.is(Vue, _Vue)
      t.is(Vuet, _Vuet)
      t.deepEqual(opt, { msg: 'ok' })
    },
    init (_vuet) {
      initCount++
      t.is(vuet, _vuet)
    },
    destroy (_vuet) {
      destroyCount++
      t.is(vuet, _vuet)
    }
  }, { msg: 'ok' })
  arr.push('testPlugin')
  t.is(useCount, 1)
  t.is(initCount, 0)
  t.is(destroyCount, 0)
  const vm = new Vue({
    vuet
  })
  t.is(useCount, 1)
  t.is(initCount, 1)
  t.is(destroyCount, 0)
  t.is(vm.$vuet, vuet)
  t.is(vm._vuet, vuet)
  vm.$destroy()
  t.is(useCount, 1)
  t.is(initCount, 1)
  t.is(destroyCount, 1)

  Vuet.use({
    name: '',
    install () {}
  })
  t.deepEqual(Object.keys(Vuet.plugins), arr)
})
<commit_msg>test:vuet base<commit_after>import test from 'ava'
import Vue from 'vue'
import Vuet from '../../dist/vuet'

Vue.use(Vuet)

const myModule = 'myModule'
const listPath = `${myModule}/route/list`
const detailPath = `${myModule}/route/detail`
const newVuet = () => {
  return new Vuet({
    data () {
      return {
        laoding: true,
        loaded: true
      }
    },
    modules: {
      [myModule]: {
        route: {
          list: {
            data () {
              return {
                list: []
              }
            },
            fetch () {
              return Promise.resolve({ list: [1, 0] })
            }
          },
          detail: {
            data () {
              return {
                detail: {
                  id: null,
                  name: '',
                  age: 0,
                  sex: ''
                }
              }
            },
            fetch () {
              return Promise.reject(new Error('Error msg'))
            }
          }
        }
      }
    }
  })
}

test('base', async t => {
  const vuet = newVuet()
  const vm = new Vue({
    vuet
  })
  const listState = {
    laoding: true,
    loaded: true,
    list: []
  }
  const detailState = {
    laoding: true,
    loaded: true,
    detail: {
      id: null,
      name: '',
      age: 0,
      sex: ''
    }
  }
  t.deepEqual(vuet.store, {
    [`${myModule}/route/list`]: listState,
    [`${myModule}/route/detail`]: detailState
  })
  t.deepEqual(vuet.getState(listPath), listState)
  t.deepEqual(vuet.getState(detailPath), detailState)
  t.deepEqual(Object.keys(vuet._options.modules), Object.keys(vuet.store))
  t.is(vuet, vm.$options.vuet)
  t.is(vuet, vm._vuet)
  t.is(vuet, vm.$vuet)

  vuet.setState(listPath, { list: [0, 1] })
  t.deepEqual(vuet.getState(listPath).list, [0, 1])

  vuet.reset(listPath)
  t.deepEqual(vuet.getState(listPath), listState)

  const store = await vuet.fetch(listPath)
  t.deepEqual(store.list, [1, 0])
  t.deepEqual(vuet.getState(listPath).list, [1, 0])

  let beforeCount = 0
  let afterCount = 0
  let path = listPath
  vuet.beforeEach((params) => {
    if (beforeCount === 3) return false
    t.is(params.path, path)
    t.is(params.store, vuet.getState(path))
    t.is(params.store, vuet.getState(params.path))
    t.deepEqual(params.params, {})
    beforeCount++
    store.laoding = true
    store.loaded = true
  })
  vuet.afterEach((err, params) => {
    if (afterCount === 2) return false
    t.is(params.path, path)
    t.is(params.store, vuet.getState(path))
    t.is(params.store, vuet.getState(params.path))
    t.deepEqual(params.params, {})
    store.loading = false
    store.loaded = (err === null)
    afterCount++
  })
  t.is(beforeCount, 0)
  t.is(afterCount, 0)
  await vuet.fetch(listPath)
  t.is(beforeCount, 1)
  t.is(afterCount, 1)
  t.false(vuet.getState(listPath).loading)
  t.true(vuet.getState(listPath).loaded)

  path = detailPath
  try {
    await vuet.fetch(detailPath)
  } catch (e) {
    t.is(String(e), 'Error: Error msg')
    t.is(beforeCount, 2)
    t.is(afterCount, 2)
    t.false(vuet.getState(listPath).loading)
    t.false(vuet.getState(listPath).loaded)
  }

  path = listPath
  const store2 = await vuet.fetch(listPath)
  t.is(beforeCount, 3)
  t.is(afterCount, 2)
  t.is(vuet.getState(listPath), store2)
  const store3 = await vuet.fetch(listPath)
  t.is(beforeCount, 3)
  t.is(afterCount, 2)
  t.is(vuet.getState(listPath), store3)
})

test('use plugins', t => {
  const vuet = newVuet()
  const arr = ['life', 'local', 'need', 'once', 'route']
  t.deepEqual(Object.keys(Vuet.plugins), arr)
  arr.forEach(name => {
    t.is(Vuet.plugins[name].name, name)
  })

  let useCount = 0
  let initCount = 0
  let destroyCount = 0
  Vuet.use({
    name: 'testPlugin',
    install (_Vue, _Vuet, opt) {
      useCount++
      t.is(Vue, _Vue)
      t.is(Vuet, _Vuet)
      t.deepEqual(opt, { msg: 'ok' })
    },
    init (_vuet) {
      initCount++
      t.is(vuet, _vuet)
    },
    destroy (_vuet) {
      destroyCount++
      t.is(vuet, _vuet)
    }
  }, { msg: 'ok' })
  arr.push('testPlugin')
  t.is(useCount, 1)
  t.is(initCount, 0)
  t.is(destroyCount, 0)
  const vm = new Vue({
    vuet
  })
  t.is(useCount, 1)
  t.is(initCount, 1)
  t.is(destroyCount, 0)
  t.is(vm.$vuet, vuet)
  t.is(vm._vuet, vuet)
  vm.$destroy()
  t.is(useCount, 1)
  t.is(initCount, 1)
  t.is(destroyCount, 1)

  Vuet.use({
    name: '',
    install () {}
  })
  t.deepEqual(Object.keys(Vuet.plugins), arr)
})
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * 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.
 */

import neo4j from '../../src/v1';
import {READ, WRITE} from '../../src/v1/driver';
import parallelLimit from 'async/parallelLimit';
import _ from 'lodash';
import {ServerVersion, VERSION_3_2_0} from '../../src/v1/internal/server-version';
import sharedNeo4j from '../internal/shared-neo4j';

describe('stress tests', () => {

  const TEST_MODES = {
    fast: {
      commandsCount: 5000,
      parallelism: 8,
      maxRunTimeMs: 10 * 60000 // 10 minutes
    },
    extended: {
      commandsCount: 2000000,
      parallelism: 16,
      maxRunTimeMs: 60 * 60000 // 60 minutes
    }
  };

  const READ_QUERY = 'MATCH (n) RETURN n LIMIT 1';
  const WRITE_QUERY = 'CREATE (person:Person:Employee {name: {name}, salary: {salary}}) RETURN person';

  const TEST_MODE = modeFromEnvOrDefault('STRESS_TEST_MODE');
  const DATABASE_URI = fromEnvOrDefault('STRESS_TEST_DATABASE_URI', 'bolt://localhost');
  const LOGGING_ENABLED = fromEnvOrDefault('STRESS_TEST_LOGGING_ENABLED', false);

  let driver;

  beforeEach(done => {
    const config = {logging: neo4j.logging.console(LOGGING_ENABLED ? 'debug' : 'info')};
    driver = neo4j.driver(DATABASE_URI, sharedNeo4j.authToken, config);

    cleanupDb(driver).then(() => done());
  });

  afterEach(done => {
    cleanupDb(driver).then(() => {
      driver.close();
      done();
    });
  });

  it('basic', done => {
    const context = new Context(driver, LOGGING_ENABLED);
    const commands = createCommands(context);

    console.time('Basic-stress-test');
    parallelLimit(commands, TEST_MODE.parallelism, error => {
      console.timeEnd('Basic-stress-test');

      if (error) {
        done.fail(error);
      }

      verifyServers(context)
        .then(() => verifyNodeCount(context))
        .then(() => done())
        .catch(error => done.fail(error));
    });
  }, TEST_MODE.maxRunTimeMs);

  function createCommands(context) {
    const uniqueCommands = createUniqueCommands(context);

    const commands = [];
    for (let i = 0; i < TEST_MODE.commandsCount; i++) {
      const randomCommand = _.sample(uniqueCommands);
      commands.push(randomCommand);
    }

    console.log(`Generated ${TEST_MODE.commandsCount} commands`);

    return commands;
  }

  function createUniqueCommands(context) {
    return [
      readQueryCommand(context),
      readQueryWithBookmarkCommand(context),
      readQueryInTxCommand(context),
      readQueryInTxWithBookmarkCommand(context),
      writeQueryCommand(context),
      writeQueryWithBookmarkCommand(context),
      writeQueryInTxCommand(context),
      writeQueryInTxWithBookmarkCommand(context)
    ];
  }

  function readQueryCommand(context) {
    return queryCommand(context, READ_QUERY, () => noParams(), READ, false);
  }

  function readQueryWithBookmarkCommand(context) {
    return queryCommand(context, READ_QUERY, () => noParams(), READ, true);
  }

  function readQueryInTxCommand(context) {
    return queryInTxCommand(context, READ_QUERY, () => noParams(), READ, false);
  }

  function readQueryInTxWithBookmarkCommand(context) {
    return queryInTxCommand(context, READ_QUERY, () => noParams(), READ, true);
  }

  function writeQueryCommand(context) {
    return queryCommand(context, WRITE_QUERY, () => randomParams(), WRITE, false);
  }

  function writeQueryWithBookmarkCommand(context) {
    return queryCommand(context, WRITE_QUERY, () => randomParams(), WRITE, true);
  }

  function writeQueryInTxCommand(context) {
    return queryInTxCommand(context, WRITE_QUERY, () => randomParams(), WRITE, false);
  }

  function writeQueryInTxWithBookmarkCommand(context) {
    return queryInTxCommand(context, WRITE_QUERY, () => randomParams(), WRITE, true);
  }

  function queryCommand(context, query, paramsSupplier, accessMode, useBookmark) {
    return callback => {
      const commandId = context.nextCommandId();
      const session = newSession(context, accessMode, useBookmark);
      const params = paramsSupplier();

      context.log(commandId, `About to run ${accessMode} query`);

      session.run(query, params).then(result => {
        context.queryCompleted(result, accessMode);
        context.log(commandId, `Query completed successfully`);

        session.close(() => {
          const possibleError = verifyQueryResult(result);
          callback(possibleError);
        });
      }).catch(error => {
        context.log(commandId, `Query failed with error ${JSON.stringify(error)}`);
        callback(error);
      });
    };
  }

  function queryInTxCommand(context, query, paramsSupplier, accessMode, useBookmark) {
    return callback => {
      const commandId = context.nextCommandId();
      const session = newSession(context, accessMode, useBookmark);
      const tx = session.beginTransaction();
      const params = paramsSupplier();

      context.log(commandId, `About to run ${accessMode} query in TX`);

      tx.run(query, params).then(result => {
        let commandError = verifyQueryResult(result);

        tx.commit().catch(commitError => {
          context.log(commandId, `Transaction commit failed with error ${JSON.stringify(error)}`);
          if (!commandError) {
            commandError = commitError;
          }
        }).then(() => {
          context.queryCompleted(result, accessMode, session.lastBookmark());
          context.log(commandId, `Transaction committed successfully`);

          session.close(() => {
            callback(commandError);
          });
        });

      }).catch(error => {
        context.log(commandId, `Query failed with error ${JSON.stringify(error)}`);
        callback(error);
      });
    };
  }

  function verifyQueryResult(result) {
    if (!result) {
      return new Error(`Received undefined result`);
    } else if (result.records.length === 0) {
      // it is ok to receive no nodes back for read queries at the beginning of the test
      return null;
    } else if (result.records.length === 1) {
      const record = result.records[0];
      return verifyRecord(record);
    } else {
      return new Error(`Unexpected amount of records received: ${JSON.stringify(result)}`);
    }
  }

  function verifyRecord(record) {
    const node = record.get(0);

    if (!arraysEqual(['Person', 'Employee'], node.labels)) {
      return new Error(`Unexpected labels in node: ${JSON.stringify(node)}`);
    }

    const propertyKeys = _.keys(node.properties);
    if (!_.isEmpty(propertyKeys) && !arraysEqual(['name', 'salary'], propertyKeys)) {
      return new Error(`Unexpected property keys in node: ${JSON.stringify(node)}`);
    }

    return null;
  }

  function verifyNodeCount(context) {
    const expectedNodeCount = context.createdNodesCount;

    const session = context.driver.session();
    return session.run('MATCH (n) RETURN count(n)').then(result => {
      const record = result.records[0];
      const count = record.get(0).toNumber();

      if (count !== expectedNodeCount) {
        throw new Error(`Unexpected node count: ${count}, expected: ${expectedNodeCount}`);
      }
    });
  }

  function verifyServers(context) {
    const routing = DATABASE_URI.indexOf('bolt+routing') === 0;

    if (routing) {
      return verifyCausalClusterMembers(context);
    }
    return verifySingleInstance(context);
  }

  function verifySingleInstance(context) {
    return new Promise(resolve => {
      const readServerAddresses = context.readServerAddresses();
      const writeServerAddresses = context.writeServerAddresses();

      expect(readServerAddresses.length).toEqual(1);
      expect(writeServerAddresses.length).toEqual(1);
      expect(readServerAddresses).toEqual(writeServerAddresses);

      const address = readServerAddresses[0];
      expect(context.readServersWithQueryCount[address]).toBeGreaterThan(1);
      expect(context.writeServersWithQueryCount[address]).toBeGreaterThan(1);

      resolve();
    });
  }

  function verifyCausalClusterMembers(context) {
    return fetchClusterAddresses(context).then(clusterAddresses => {
      // before 3.2.0 only read replicas serve reads
      const readsOnFollowersEnabled = context.serverVersion.compareTo(VERSION_3_2_0) >= 0;

      if (readsOnFollowersEnabled) {
        // expect all followers to serve more than zero read queries
        assertAllAddressesServedReadQueries(clusterAddresses.followers, context.readServersWithQueryCount);
      }

      // expect all read replicas to serve more than zero read queries
      assertAllAddressesServedReadQueries(clusterAddresses.readReplicas, context.readServersWithQueryCount);

      if (readsOnFollowersEnabled) {
        // expect all followers to serve same order of magnitude read queries
        assertAllAddressesServedSimilarAmountOfReadQueries(clusterAddresses.followers, context.readServersWithQueryCount);
      }

      // expect all read replicas to serve same order of magnitude read queries
      assertAllAddressesServedSimilarAmountOfReadQueries(clusterAddresses.readReplicas,
        context.readServersWithQueryCount);
    });
  }

  function fetchClusterAddresses(context) {
    const session = context.driver.session();
    return session.run('CALL dbms.cluster.overview()').then(result => {
      session.close();
      const records = result.records;

      const followers = addressesWithRole(records, 'FOLLOWER');
      const readReplicas = addressesWithRole(records, 'READ_REPLICA');

      return new ClusterAddresses(followers, readReplicas);
    });
  }

  function addressesWithRole(records, role) {
    return _.uniq(records.filter(record => record.get('role') === role)
      .map(record => record.get('addresses')[0].replace('bolt://', '')));
  }

  function assertAllAddressesServedReadQueries(addresses, readQueriesByServer) {
    addresses.forEach(address => {
      const queries = readQueriesByServer[address];
      expect(queries).toBeGreaterThan(0);
    });
  }

  function assertAllAddressesServedSimilarAmountOfReadQueries(addresses, readQueriesByServer) {
    const expectedOrderOfMagnitude = orderOfMagnitude(readQueriesByServer[addresses[0]]);

    addresses.forEach(address => {
      const queries = readQueriesByServer[address];
      const currentOrderOfMagnitude = orderOfMagnitude(queries);

      expect(currentOrderOfMagnitude).not.toBeLessThan(expectedOrderOfMagnitude - 1);
      expect(currentOrderOfMagnitude).not.toBeGreaterThan(expectedOrderOfMagnitude + 1);
    });
  }

  function orderOfMagnitude(number) {
    let result = 1;
    while (number >= 10) {
      number /= 10;
      result++;
    }
    return result;
  }

  function randomParams() {
    return {
      name: `Person-${Date.now()}`,
      salary: Date.now()
    };
  }

  function noParams() {
    return {};
  }

  function newSession(context, accessMode, useBookmark) {
    if (useBookmark) {
      return context.driver.session(accessMode, context.bookmark);
    }
    return context.driver.session(accessMode);
  }

  function modeFromEnvOrDefault(envVariableName) {
    const modeName = fromEnvOrDefault(envVariableName, 'fast');
    const mode = TEST_MODES[modeName];
    if (!mode) {
      throw new Error(`Unknown test mode: ${modeName}`);
    }
    console.log(`Selected '${modeName}' mode for the stress test`);
    return mode;
  }

  function fromEnvOrDefault(envVariableName, defaultValue) {
    if (process && process.env && process.env[envVariableName]) {
      return process.env[envVariableName];
    }
    return defaultValue;
  }

  function cleanupDb(driver) {
    const session = driver.session();
    return session.run('MATCH (n) DETACH DELETE n').then(() => {
      session.close();
    }).catch(error => {
      console.log('Error clearing the database: ', error);
    });
  }

  function arraysEqual(array1, array2) {
    return _.difference(array1, array2).length === 0;
  }

  class Context {

    constructor(driver, loggingEnabled) {
      this.driver = driver;
      this.bookmark = null;
      this.createdNodesCount = 0;
      this._commandIdCouter = 0;
      this._loggingEnabled = loggingEnabled;
      this.readServersWithQueryCount = {};
      this.writeServersWithQueryCount = {};
      this.serverVersion = null;
    }

    queryCompleted(result, accessMode, bookmark) {
      const serverInfo = result.summary.server;

      if (!this.serverVersion) {
        this.serverVersion = ServerVersion.fromString(serverInfo.version);
      }

      const serverAddress = serverInfo.address;
      if (accessMode === WRITE) {
        this.createdNodesCount++;
        this.writeServersWithQueryCount[serverAddress] = (this.writeServersWithQueryCount[serverAddress] || 0) + 1;
      } else {
        this.readServersWithQueryCount[serverAddress] = (this.readServersWithQueryCount[serverAddress] || 0) + 1;
      }

      if (bookmark) {
        this.bookmark = bookmark;
      }
    }

    nextCommandId() {
      return this._commandIdCouter++;
    }

    readServerAddresses() {
      return Object.keys(this.readServersWithQueryCount);
    }

    writeServerAddresses() {
      return Object.keys(this.writeServersWithQueryCount);
    }

    log(commandId, message) {
      if (this._loggingEnabled) {
        console.log(`Command [${commandId}]: ${message}`);
      }
    }
  }

  class ClusterAddresses {

    constructor(followers, readReplicas) {
      this.followers = followers;
      this.readReplicas = readReplicas;
    }
  }

});
<commit_msg>Smaller number of commands in extended stress test<commit_after>/**
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * 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.
 */

import neo4j from '../../src/v1';
import {READ, WRITE} from '../../src/v1/driver';
import parallelLimit from 'async/parallelLimit';
import _ from 'lodash';
import {ServerVersion, VERSION_3_2_0} from '../../src/v1/internal/server-version';
import sharedNeo4j from '../internal/shared-neo4j';

describe('stress tests', () => {

  const TEST_MODES = {
    fast: {
      commandsCount: 5000,
      parallelism: 8,
      maxRunTimeMs: 10 * 60000 // 10 minutes
    },
    extended: {
      commandsCount: 1000000,
      parallelism: 16,
      maxRunTimeMs: 60 * 60000 // 60 minutes
    }
  };

  const READ_QUERY = 'MATCH (n) RETURN n LIMIT 1';
  const WRITE_QUERY = 'CREATE (person:Person:Employee {name: {name}, salary: {salary}}) RETURN person';

  const TEST_MODE = modeFromEnvOrDefault('STRESS_TEST_MODE');
  const DATABASE_URI = fromEnvOrDefault('STRESS_TEST_DATABASE_URI', 'bolt://localhost');
  const LOGGING_ENABLED = fromEnvOrDefault('STRESS_TEST_LOGGING_ENABLED', false);

  let driver;

  beforeEach(done => {
    const config = {logging: neo4j.logging.console(LOGGING_ENABLED ? 'debug' : 'info')};
    driver = neo4j.driver(DATABASE_URI, sharedNeo4j.authToken, config);

    cleanupDb(driver).then(() => done());
  });

  afterEach(done => {
    cleanupDb(driver).then(() => {
      driver.close();
      done();
    });
  });

  it('basic', done => {
    const context = new Context(driver, LOGGING_ENABLED);
    const commands = createCommands(context);

    console.time('Basic-stress-test');
    parallelLimit(commands, TEST_MODE.parallelism, error => {
      console.timeEnd('Basic-stress-test');

      if (error) {
        done.fail(error);
      }

      verifyServers(context)
        .then(() => verifyNodeCount(context))
        .then(() => done())
        .catch(error => done.fail(error));
    });
  }, TEST_MODE.maxRunTimeMs);

  function createCommands(context) {
    const uniqueCommands = createUniqueCommands(context);

    const commands = [];
    for (let i = 0; i < TEST_MODE.commandsCount; i++) {
      const randomCommand = _.sample(uniqueCommands);
      commands.push(randomCommand);
    }

    console.log(`Generated ${TEST_MODE.commandsCount} commands`);

    return commands;
  }

  function createUniqueCommands(context) {
    return [
      readQueryCommand(context),
      readQueryWithBookmarkCommand(context),
      readQueryInTxCommand(context),
      readQueryInTxWithBookmarkCommand(context),
      writeQueryCommand(context),
      writeQueryWithBookmarkCommand(context),
      writeQueryInTxCommand(context),
      writeQueryInTxWithBookmarkCommand(context)
    ];
  }

  function readQueryCommand(context) {
    return queryCommand(context, READ_QUERY, () => noParams(), READ, false);
  }

  function readQueryWithBookmarkCommand(context) {
    return queryCommand(context, READ_QUERY, () => noParams(), READ, true);
  }

  function readQueryInTxCommand(context) {
    return queryInTxCommand(context, READ_QUERY, () => noParams(), READ, false);
  }

  function readQueryInTxWithBookmarkCommand(context) {
    return queryInTxCommand(context, READ_QUERY, () => noParams(), READ, true);
  }

  function writeQueryCommand(context) {
    return queryCommand(context, WRITE_QUERY, () => randomParams(), WRITE, false);
  }

  function writeQueryWithBookmarkCommand(context) {
    return queryCommand(context, WRITE_QUERY, () => randomParams(), WRITE, true);
  }

  function writeQueryInTxCommand(context) {
    return queryInTxCommand(context, WRITE_QUERY, () => randomParams(), WRITE, false);
  }

  function writeQueryInTxWithBookmarkCommand(context) {
    return queryInTxCommand(context, WRITE_QUERY, () => randomParams(), WRITE, true);
  }

  function queryCommand(context, query, paramsSupplier, accessMode, useBookmark) {
    return callback => {
      const commandId = context.nextCommandId();
      const session = newSession(context, accessMode, useBookmark);
      const params = paramsSupplier();

      context.log(commandId, `About to run ${accessMode} query`);

      session.run(query, params).then(result => {
        context.queryCompleted(result, accessMode);
        context.log(commandId, `Query completed successfully`);

        session.close(() => {
          const possibleError = verifyQueryResult(result);
          callback(possibleError);
        });
      }).catch(error => {
        context.log(commandId, `Query failed with error ${JSON.stringify(error)}`);
        callback(error);
      });
    };
  }

  function queryInTxCommand(context, query, paramsSupplier, accessMode, useBookmark) {
    return callback => {
      const commandId = context.nextCommandId();
      const session = newSession(context, accessMode, useBookmark);
      const tx = session.beginTransaction();
      const params = paramsSupplier();

      context.log(commandId, `About to run ${accessMode} query in TX`);

      tx.run(query, params).then(result => {
        let commandError = verifyQueryResult(result);

        tx.commit().catch(commitError => {
          context.log(commandId, `Transaction commit failed with error ${JSON.stringify(error)}`);
          if (!commandError) {
            commandError = commitError;
          }
        }).then(() => {
          context.queryCompleted(result, accessMode, session.lastBookmark());
          context.log(commandId, `Transaction committed successfully`);

          session.close(() => {
            callback(commandError);
          });
        });

      }).catch(error => {
        context.log(commandId, `Query failed with error ${JSON.stringify(error)}`);
        callback(error);
      });
    };
  }

  function verifyQueryResult(result) {
    if (!result) {
      return new Error(`Received undefined result`);
    } else if (result.records.length === 0) {
      // it is ok to receive no nodes back for read queries at the beginning of the test
      return null;
    } else if (result.records.length === 1) {
      const record = result.records[0];
      return verifyRecord(record);
    } else {
      return new Error(`Unexpected amount of records received: ${JSON.stringify(result)}`);
    }
  }

  function verifyRecord(record) {
    const node = record.get(0);

    if (!arraysEqual(['Person', 'Employee'], node.labels)) {
      return new Error(`Unexpected labels in node: ${JSON.stringify(node)}`);
    }

    const propertyKeys = _.keys(node.properties);
    if (!_.isEmpty(propertyKeys) && !arraysEqual(['name', 'salary'], propertyKeys)) {
      return new Error(`Unexpected property keys in node: ${JSON.stringify(node)}`);
    }

    return null;
  }

  function verifyNodeCount(context) {
    const expectedNodeCount = context.createdNodesCount;

    const session = context.driver.session();
    return session.run('MATCH (n) RETURN count(n)').then(result => {
      const record = result.records[0];
      const count = record.get(0).toNumber();

      if (count !== expectedNodeCount) {
        throw new Error(`Unexpected node count: ${count}, expected: ${expectedNodeCount}`);
      }
    });
  }

  function verifyServers(context) {
    const routing = DATABASE_URI.indexOf('bolt+routing') === 0;

    if (routing) {
      return verifyCausalClusterMembers(context);
    }
    return verifySingleInstance(context);
  }

  function verifySingleInstance(context) {
    return new Promise(resolve => {
      const readServerAddresses = context.readServerAddresses();
      const writeServerAddresses = context.writeServerAddresses();

      expect(readServerAddresses.length).toEqual(1);
      expect(writeServerAddresses.length).toEqual(1);
      expect(readServerAddresses).toEqual(writeServerAddresses);

      const address = readServerAddresses[0];
      expect(context.readServersWithQueryCount[address]).toBeGreaterThan(1);
      expect(context.writeServersWithQueryCount[address]).toBeGreaterThan(1);

      resolve();
    });
  }

  function verifyCausalClusterMembers(context) {
    return fetchClusterAddresses(context).then(clusterAddresses => {
      // before 3.2.0 only read replicas serve reads
      const readsOnFollowersEnabled = context.serverVersion.compareTo(VERSION_3_2_0) >= 0;

      if (readsOnFollowersEnabled) {
        // expect all followers to serve more than zero read queries
        assertAllAddressesServedReadQueries(clusterAddresses.followers, context.readServersWithQueryCount);
      }

      // expect all read replicas to serve more than zero read queries
      assertAllAddressesServedReadQueries(clusterAddresses.readReplicas, context.readServersWithQueryCount);

      if (readsOnFollowersEnabled) {
        // expect all followers to serve same order of magnitude read queries
        assertAllAddressesServedSimilarAmountOfReadQueries(clusterAddresses.followers, context.readServersWithQueryCount);
      }

      // expect all read replicas to serve same order of magnitude read queries
      assertAllAddressesServedSimilarAmountOfReadQueries(clusterAddresses.readReplicas,
        context.readServersWithQueryCount);
    });
  }

  function fetchClusterAddresses(context) {
    const session = context.driver.session();
    return session.run('CALL dbms.cluster.overview()').then(result => {
      session.close();
      const records = result.records;

      const followers = addressesWithRole(records, 'FOLLOWER');
      const readReplicas = addressesWithRole(records, 'READ_REPLICA');

      return new ClusterAddresses(followers, readReplicas);
    });
  }

  function addressesWithRole(records, role) {
    return _.uniq(records.filter(record => record.get('role') === role)
      .map(record => record.get('addresses')[0].replace('bolt://', '')));
  }

  function assertAllAddressesServedReadQueries(addresses, readQueriesByServer) {
    addresses.forEach(address => {
      const queries = readQueriesByServer[address];
      expect(queries).toBeGreaterThan(0);
    });
  }

  function assertAllAddressesServedSimilarAmountOfReadQueries(addresses, readQueriesByServer) {
    const expectedOrderOfMagnitude = orderOfMagnitude(readQueriesByServer[addresses[0]]);

    addresses.forEach(address => {
      const queries = readQueriesByServer[address];
      const currentOrderOfMagnitude = orderOfMagnitude(queries);

      expect(currentOrderOfMagnitude).not.toBeLessThan(expectedOrderOfMagnitude - 1);
      expect(currentOrderOfMagnitude).not.toBeGreaterThan(expectedOrderOfMagnitude + 1);
    });
  }

  function orderOfMagnitude(number) {
    let result = 1;
    while (number >= 10) {
      number /= 10;
      result++;
    }
    return result;
  }

  function randomParams() {
    return {
      name: `Person-${Date.now()}`,
      salary: Date.now()
    };
  }

  function noParams() {
    return {};
  }

  function newSession(context, accessMode, useBookmark) {
    if (useBookmark) {
      return context.driver.session(accessMode, context.bookmark);
    }
    return context.driver.session(accessMode);
  }

  function modeFromEnvOrDefault(envVariableName) {
    const modeName = fromEnvOrDefault(envVariableName, 'fast');
    const mode = TEST_MODES[modeName];
    if (!mode) {
      throw new Error(`Unknown test mode: ${modeName}`);
    }
    console.log(`Selected '${modeName}' mode for the stress test`);
    return mode;
  }

  function fromEnvOrDefault(envVariableName, defaultValue) {
    if (process && process.env && process.env[envVariableName]) {
      return process.env[envVariableName];
    }
    return defaultValue;
  }

  function cleanupDb(driver) {
    const session = driver.session();
    return session.run('MATCH (n) DETACH DELETE n').then(() => {
      session.close();
    }).catch(error => {
      console.log('Error clearing the database: ', error);
    });
  }

  function arraysEqual(array1, array2) {
    return _.difference(array1, array2).length === 0;
  }

  class Context {

    constructor(driver, loggingEnabled) {
      this.driver = driver;
      this.bookmark = null;
      this.createdNodesCount = 0;
      this._commandIdCouter = 0;
      this._loggingEnabled = loggingEnabled;
      this.readServersWithQueryCount = {};
      this.writeServersWithQueryCount = {};
      this.serverVersion = null;
    }

    queryCompleted(result, accessMode, bookmark) {
      const serverInfo = result.summary.server;

      if (!this.serverVersion) {
        this.serverVersion = ServerVersion.fromString(serverInfo.version);
      }

      const serverAddress = serverInfo.address;
      if (accessMode === WRITE) {
        this.createdNodesCount++;
        this.writeServersWithQueryCount[serverAddress] = (this.writeServersWithQueryCount[serverAddress] || 0) + 1;
      } else {
        this.readServersWithQueryCount[serverAddress] = (this.readServersWithQueryCount[serverAddress] || 0) + 1;
      }

      if (bookmark) {
        this.bookmark = bookmark;
      }
    }

    nextCommandId() {
      return this._commandIdCouter++;
    }

    readServerAddresses() {
      return Object.keys(this.readServersWithQueryCount);
    }

    writeServerAddresses() {
      return Object.keys(this.writeServersWithQueryCount);
    }

    log(commandId, message) {
      if (this._loggingEnabled) {
        console.log(`Command [${commandId}]: ${message}`);
      }
    }
  }

  class ClusterAddresses {

    constructor(followers, readReplicas) {
      this.followers = followers;
      this.readReplicas = readReplicas;
    }
  }

});
<|endoftext|>


<commit_before>class SVGCreator {
  constructor(height, width) {
    this.height = height;
    this.width = width;
    this.children = [];
    this.scripts = [];
  }

  addScript(script) {
    this.script.push(script);
  }

  addText(text, attributes = {}) {
    return addText(this.children, text, attributes);
  }

  add(kind, attributes = {}) {
    return add(this.children, kind, attributes);
  }

  toSVG() {
    let content = `<svg width="${this.width}" height="${this.height}">`;
    content += getChildrenSVG(this.children);
    content += '</svg>';
    return content;
  }
}

function getChildrenSVG(children) {
  let content = '';
  for (let element of children) {
    let attributes = Object.keys(element.attributes)
      .map(
        (key) =>
          `${key.replace(/([A-Z])/g, (match) => '-' + match.toLowerCase())}="${
            element.attributes[key]
          }"`,
      )
      .join(' ');
    if (element.children.length > 0) {
      content += `<${element.kind} ${attributes}>${getChildrenSVG(
        element.children,
      )}</${element.kind}>`;
    } else if (element.innerHTML) {
      console.log(element.innerHTML);
      content += `<${element.kind} ${attributes}>${element.innerHTML}</${element.kind}>`;
    } else {
      content += `<${element.kind} ${attributes} />`;
    }
  }
  return content;
}

class Element {
  constructor(kind = '', innerHTML = '', attributes = {}) {
    this.children = [];
    this.kind = kind;
    this.attributes = attributes;
    this.innerHTML = innerHTML;
  }

  addText(text, attributes = {}) {
    return addText(this.children, text, attributes);
  }

  add(kind, attributes = {}) {
    return add(this.children, kind, attributes);
  }
}

function encodeText(string) {
  return string.replace(/[\u00A0-\u9999<>\&]/gim, function (i) {
    return '&#' + i.charCodeAt(0) + ';';
  });
}

function addText(children, text, attributes = {}) {
  let element = new Element('text', encodeText(text), attributes);
  children.push(element);
  return element;
}

function add(children, kind, attributes = {}) {
  let element = new Element(kind, '', attributes);
  children.push(element);
  return element;
}

module.exports = SVGCreator;
<commit_msg>feat: improve SVGCreator<commit_after>class SVGCreator {
  constructor(height, width) {
    this.height = height;
    this.width = width;
    this.children = [];
    this.scripts = [];
  }

  addScript(script) {
    this.script.push(script);
  }

  addText(text, attributes = {}) {
    return addText(this.children, text, attributes);
  }

  add(kind, attributes = {}) {
    return add(this.children, kind, attributes);
  }

  toSVG() {
    let content = `<svg width="${this.width}" height="${this.height}">`;
    content += getChildrenSVG(this.children);
    content += '</svg>';
    return content;
  }
}

function getChildrenSVG(children) {
  let content = '';
  for (let element of children) {
    let attributes = [];
    appendAttributes(element.attributes, attributes);
    console.log(attributes);
    attributes = attributes.join(' ');
    if (element.children.length > 0) {
      content += `<${element.kind} ${attributes}>${getChildrenSVG(
        element.children,
      )}</${element.kind}>`;
    } else if (element.innerHTML) {
      console.log(element.innerHTML);
      content += `<${element.kind} ${attributes}>${element.innerHTML}</${element.kind}>`;
    } else {
      content += `<${element.kind} ${attributes} />`;
    }
  }
  return content;
}

class Element {
  constructor(kind = '', innerHTML = '', attributes = {}) {
    this.children = [];
    this.kind = kind;
    this.attributes = attributes;
    this.innerHTML = innerHTML;
  }

  addText(text, attributes = {}) {
    return addText(this.children, text, attributes);
  }

  add(kind, attributes = {}) {
    return add(this.children, kind, attributes);
  }
}

function encodeText(string) {
  return string.replace(/[\u00A0-\u9999<>\&]/gim, function (i) {
    return '&#' + i.charCodeAt(0) + ';';
  });
}

function addText(children, text, attributes = {}) {
  let element = new Element('text', encodeText(text), attributes);
  children.push(element);
  return element;
}

function add(children, kind, attributes = {}) {
  let element = new Element(kind, '', attributes);
  children.push(element);
  return element;
}

// attributes may contain objects to organize the properties
function appendAttributes(newAttributes, attributes) {
  for (let key in newAttributes) {
    if (typeof newAttributes[key] === 'object') {
      appendAttributes(newAttributes[key], attributes);
    } else {
      let newKey = key.replace(
        /([A-Z])/g,
        (match) => '-' + match.toLowerCase(),
      );
      attributes.push(newKey + '="' + newAttributes[key] + '"');
    }
  }
}

module.exports = SVGCreator;
<|endoftext|>