<commit_before>import { DateTime, Interval } from "luxon";
import PropTypes from "prop-types";
import React, { useCallback, useMemo, useState } from "react";
import { useLocation } from "react-router-dom";
import styled from "styled-components";
import { mutate } from "swr";
import { RawFeatherIcon } from "@agir/front/genericComponents/FeatherIcon";

import { useSelector } from "@agir/front/globalContext/GlobalContext";
import {
  getIsConnected,
  getRoutes,
  getUser,
} from "@agir/front/globalContext/reducers";
import * as api from "@agir/events/common/api";

import Button from "@agir/front/genericComponents/Button";
import Link from "@agir/front/app/Link";
import { Hide } from "@agir/front/genericComponents/grid";
import SubscriptionTypeModal from "@agir/front/authentication/SubscriptionTypeModal";

import style from "@agir/front/genericComponents/_variables.scss";
import { displayHumanDate } from "@agir/lib/utils/time";

import QuitEventButton from "./QuitEventButton";

import logger from "@agir/lib/utils/logger";

const log = logger(__filename);

const EventHeaderContainer = styled.div`
  @media (min-width: ${style.collapse}px) {
    margin-bottom: 4rem;
  }
  > * {
    margin: 0.5rem 0;
  }
`;

const EventTitle = styled.h1`
  font-size: 1.75rem;
  line-height: 1.4;
  font-weight: 700;
  margin-bottom: 0;

  @media (max-width: ${style.collapse}px) {
    margin-bottom: 1rem;
    font-size: 1.25rem;
  }
`;

const EventDate = styled.div`
  margin: 0.5rem 0;
  font-weight: 500;
`;

const SmallText = styled.div`
  font-size: 0.81rem;
  font-color: ${style.black500};
`;

const ActionButton = styled(Button)``;
const ActionLink = styled(Link)`
  font-weight: 700;
  text-decoration: underline;
`;
const ActionDetails = styled.div`
  margin-top: 0;
  font-size: 1rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;

  div {
    display: inline-flex;
    align-items: center;
  }
`;

const StyledActionButtons = styled.div`
  display: inline-grid;
  grid-gap: 0.5rem;
  grid-template-columns: auto auto;
  padding: 0.5rem 0;

  @media (max-width: ${style.collapse}px) {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  }

  ${Button} {
    margin: 0;
    justify-content: center;

    && *,
    && *::before {
      flex: 0 0 auto;
    }
  }

  ${Button} + ${Button} {
    margin-left: 0;
  }
`;

const RSVPButton = (props) => {
  const user = useSelector(getUser);
  const { id, forUsers, hasPrice, routes } = props;

  const [isLoading, setIsLoading] = useState(false);
  const [hasSubscriptionTypeModal, setHasSubscriptionTypeModal] = useState(
    false
  );

  const openSubscriptionTypeModal = useCallback(() => {
    setHasSubscriptionTypeModal(true);
  }, []);

  const closeSubscriptionTypeModal = useCallback(() => {
    setHasSubscriptionTypeModal(false);
  }, []);

  const handleRSVP = useCallback(
    async (e) => {
      e && e.preventDefault();
      setIsLoading(true);

      if (hasPrice) {
        log.debug("Has price, redirection.");
        window.location.href = routes.rsvp;
        return;
      }

      try {
        const response = await api.rsvpEvent(id);
        if (response.error) {
          log.error(response.error);
          await mutate(api.getEventEndpoint("getEvent", { eventPk: id }));
        }
      } catch (err) {
        window.location.reload();
      }

      setIsLoading(false);
      setHasSubscriptionTypeModal(false);

      await mutate(
        api.getEventEndpoint("getEvent", { eventPk: id }),
        (event) => ({
          ...event,
          rsvped: true,
        })
      );
    },
    [id, hasPrice, routes]
  );

  const shouldUpdateSubscription = useMemo(() => {
    if (!user) {
      return null;
    }
    if (forUsers === "2" && user.is2022) {
      return null;
    }
    if (forUsers === "I" && user.isInsoumise) {
      return null;
    }
    return forUsers === "2" ? "NSP" : "LFI";
  }, [user, forUsers]);

  return (

  );
};

const ActionButtons = (props) => {
  const {
    id,
    name,
    past,
    rsvped,
    logged,
    isOrganizer,
    routes,
    onlineUrl,
  } = props;

  if (past) {
    return (

    );
  }

  if (!logged) {
    return (

    );
  }

  if (rsvped) {
    return (
      <>

        <ActionDetails>

            <RawFeatherIcon name="check" color="green" />  Vous participez
            à l'évènement

          <QuitEventButton id={id} name={name} />
        </ActionDetails>
      </>
    );
  }

  return <RSVPButton {...props} />;
};
RSVPButton.propTypes = ActionButtons.propTypes = {
  id: PropTypes.string,
  hasSubscriptionForm: PropTypes.bool,
  past: PropTypes.bool,
  rsvped: PropTypes.bool,
  logged: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  routes: PropTypes.shape({
    manage: PropTypes.string,
    rsvp: PropTypes.string,
  }),
};

const AdditionalMessage = ({
  id,
  name,
  isOrganizer,
  logged,
  rsvped,
  price,
  forUsers,
}) => {
  const location = useLocation();

  if (!logged) {
    return (

        <ActionLink
          route="login"
          params={{ from: "event", forUsers, next: location.pathname }}
        >
          Je me connecte
        </ActionLink>{" "}
        ou{" "}
        <ActionLink
          route="signup"
          params={{ from: "event", forUsers, next: location.pathname }}
        >
          je m'inscris
        </ActionLink>{" "}
        pour participer à l'événement

    );
  }

  if (price) {
    return (
      <SmallText>
        Entrée :
        {price}
      </SmallText>
    );
  }

  if (!isOrganizer) {
    return (
      <SmallText>Votre email sera communiqué à l'organisateur·ice</SmallText>
    );
  }

  return <></>;
};
AdditionalMessage.propTypes = {
  id: PropTypes.string,
  name: PropTypes.string,
  hasSubscriptionForm: PropTypes.bool,
  past: PropTypes.bool,
  rsvped: PropTypes.bool,
  logged: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  price: PropTypes.string,
  routes: PropTypes.object,
  forUsers: PropTypes.string,
};

const EventHeader = ({
  id,
  name,
  rsvp,
  options,
  schedule,
  routes,
  isOrganizer,
  forUsers,
  hasRightSubscription,
  onlineUrl,
}) => {
  const globalRoutes = useSelector(getRoutes);
  const logged = useSelector(getIsConnected);

  const rsvped = rsvp === "CO";
  const now = DateTime.local();
  const past = now > schedule.end;
  let eventString = displayHumanDate(schedule.start);
  eventString = eventString.slice(0, 1).toUpperCase() + eventString.slice(1);

  return (
    <EventHeaderContainer>
      <EventTitle>{name}</EventTitle>
      <Hide under>
        <EventDate>{eventString}</EventDate>
      </Hide>
      <ActionButtons
        id={id}
        past={past}
        logged={logged}
        rsvped={rsvped}
        routes={routes}
        isOrganizer={isOrganizer}
        forUsers={forUsers}
        hasRightSubscription={hasRightSubscription}
        hasPrice={!!options && !!options.price}
        onlineUrl={onlineUrl}
      />
      {!past && (
        <AdditionalMessage
          id={id}
          name={name}
          isOrganizer={isOrganizer}
          past={past}
          logged={logged}
          rsvped={rsvped}
          price={options.price}
          routes={{ ...routes, ...globalRoutes }}
          forUsers={forUsers}
        />
      )}
    </EventHeaderContainer>
  );
};

EventHeader.propTypes = {
  id: PropTypes.string,
  name: PropTypes.string,
  startTime: PropTypes.instanceOf(DateTime),
  endTime: PropTypes.instanceOf(DateTime),
  schedule: PropTypes.instanceOf(Interval),
  hasSubscriptionForm: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  options: PropTypes.shape({
    price: PropTypes.string,
  }),
  rsvp: PropTypes.string,
  routes: PropTypes.object,
  forUsers: PropTypes.string,
  hasRightSubscription: PropTypes.bool,
};

export default EventHeader;
<commit_msg>style: eventheader margin<commit_after>import { DateTime, Interval } from "luxon";
import PropTypes from "prop-types";
import React, { useCallback, useMemo, useState } from "react";
import { useLocation } from "react-router-dom";
import styled from "styled-components";
import { mutate } from "swr";
import { RawFeatherIcon } from "@agir/front/genericComponents/FeatherIcon";

import { useSelector } from "@agir/front/globalContext/GlobalContext";
import {
  getIsConnected,
  getRoutes,
  getUser,
} from "@agir/front/globalContext/reducers";
import * as api from "@agir/events/common/api";

import Button from "@agir/front/genericComponents/Button";
import Link from "@agir/front/app/Link";
import { Hide } from "@agir/front/genericComponents/grid";
import SubscriptionTypeModal from "@agir/front/authentication/SubscriptionTypeModal";

import style from "@agir/front/genericComponents/_variables.scss";
import { displayHumanDate } from "@agir/lib/utils/time";

import QuitEventButton from "./QuitEventButton";

import logger from "@agir/lib/utils/logger";

const log = logger(__filename);

const EventHeaderContainer = styled.div`
  @media (min-width: ${style.collapse}px) {
    margin-bottom: 2rem;
  }
  > * {
    margin: 0.5rem 0;
  }
`;

const EventTitle = styled.h1`
  font-size: 1.75rem;
  line-height: 1.4;
  font-weight: 700;
  margin-bottom: 0;

  @media (max-width: ${style.collapse}px) {
    margin-bottom: 1rem;
    font-size: 1.25rem;
  }
`;

const EventDate = styled.div`
  margin: 0.5rem 0;
  font-weight: 500;
`;

const SmallText = styled.div`
  font-size: 0.81rem;
  font-color: ${style.black500};
`;

const ActionButton = styled(Button)``;
const ActionLink = styled(Link)`
  font-weight: 700;
  text-decoration: underline;
`;
const ActionDetails = styled.div`
  margin-top: 0;
  font-size: 1rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;

  div {
    display: inline-flex;
    align-items: center;
  }
`;

const StyledActionButtons = styled.div`
  display: inline-grid;
  grid-gap: 0.5rem;
  grid-template-columns: auto auto;
  padding: 0.5rem 0;

  @media (max-width: ${style.collapse}px) {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  }

  ${Button} {
    margin: 0;
    justify-content: center;

    && *,
    && *::before {
      flex: 0 0 auto;
    }
  }

  ${Button} + ${Button} {
    margin-left: 0;
  }
`;

const RSVPButton = (props) => {
  const user = useSelector(getUser);
  const { id, forUsers, hasPrice, routes } = props;

  const [isLoading, setIsLoading] = useState(false);
  const [hasSubscriptionTypeModal, setHasSubscriptionTypeModal] = useState(
    false
  );

  const openSubscriptionTypeModal = useCallback(() => {
    setHasSubscriptionTypeModal(true);
  }, []);

  const closeSubscriptionTypeModal = useCallback(() => {
    setHasSubscriptionTypeModal(false);
  }, []);

  const handleRSVP = useCallback(
    async (e) => {
      e && e.preventDefault();
      setIsLoading(true);

      if (hasPrice) {
        log.debug("Has price, redirection.");
        window.location.href = routes.rsvp;
        return;
      }

      try {
        const response = await api.rsvpEvent(id);
        if (response.error) {
          log.error(response.error);
          await mutate(api.getEventEndpoint("getEvent", { eventPk: id }));
        }
      } catch (err) {
        window.location.reload();
      }

      setIsLoading(false);
      setHasSubscriptionTypeModal(false);

      await mutate(
        api.getEventEndpoint("getEvent", { eventPk: id }),
        (event) => ({
          ...event,
          rsvped: true,
        })
      );
    },
    [id, hasPrice, routes]
  );

  const shouldUpdateSubscription = useMemo(() => {
    if (!user) {
      return null;
    }
    if (forUsers === "2" && user.is2022) {
      return null;
    }
    if (forUsers === "I" && user.isInsoumise) {
      return null;
    }
    return forUsers === "2" ? "NSP" : "LFI";
  }, [user, forUsers]);

  return (

  );
};

const ActionButtons = (props) => {
  const {
    id,
    name,
    past,
    rsvped,
    logged,
    isOrganizer,
    routes,
    onlineUrl,
  } = props;

  if (past) {
    return (

    );
  }

  if (!logged) {
    return (

    );
  }

  if (rsvped) {
    return (
      <>

        <ActionDetails>

            <RawFeatherIcon name="check" color="green" />  Vous participez
            à l'évènement

          <QuitEventButton id={id} name={name} />
        </ActionDetails>
      </>
    );
  }

  return <RSVPButton {...props} />;
};
RSVPButton.propTypes = ActionButtons.propTypes = {
  id: PropTypes.string,
  hasSubscriptionForm: PropTypes.bool,
  past: PropTypes.bool,
  rsvped: PropTypes.bool,
  logged: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  routes: PropTypes.shape({
    manage: PropTypes.string,
    rsvp: PropTypes.string,
  }),
};

const AdditionalMessage = ({
  id,
  name,
  isOrganizer,
  logged,
  rsvped,
  price,
  forUsers,
}) => {
  const location = useLocation();

  if (!logged) {
    return (

        <ActionLink
          route="login"
          params={{ from: "event", forUsers, next: location.pathname }}
        >
          Je me connecte
        </ActionLink>{" "}
        ou{" "}
        <ActionLink
          route="signup"
          params={{ from: "event", forUsers, next: location.pathname }}
        >
          je m'inscris
        </ActionLink>{" "}
        pour participer à l'événement

    );
  }

  if (price) {
    return (
      <SmallText>
        Entrée :
        {price}
      </SmallText>
    );
  }

  if (!isOrganizer) {
    return (
      <SmallText>Votre email sera communiqué à l'organisateur·ice</SmallText>
    );
  }

  return <></>;
};
AdditionalMessage.propTypes = {
  id: PropTypes.string,
  name: PropTypes.string,
  hasSubscriptionForm: PropTypes.bool,
  past: PropTypes.bool,
  rsvped: PropTypes.bool,
  logged: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  price: PropTypes.string,
  routes: PropTypes.object,
  forUsers: PropTypes.string,
};

const EventHeader = ({
  id,
  name,
  rsvp,
  options,
  schedule,
  routes,
  isOrganizer,
  forUsers,
  hasRightSubscription,
  onlineUrl,
}) => {
  const globalRoutes = useSelector(getRoutes);
  const logged = useSelector(getIsConnected);

  const rsvped = rsvp === "CO";
  const now = DateTime.local();
  const past = now > schedule.end;
  let eventString = displayHumanDate(schedule.start);
  eventString = eventString.slice(0, 1).toUpperCase() + eventString.slice(1);

  return (
    <EventHeaderContainer>
      <EventTitle>{name}</EventTitle>
      <Hide under>
        <EventDate>{eventString}</EventDate>
      </Hide>
      <ActionButtons
        id={id}
        past={past}
        logged={logged}
        rsvped={rsvped}
        routes={routes}
        isOrganizer={isOrganizer}
        forUsers={forUsers}
        hasRightSubscription={hasRightSubscription}
        hasPrice={!!options && !!options.price}
        onlineUrl={onlineUrl}
      />
      {!past && (
        <AdditionalMessage
          id={id}
          name={name}
          isOrganizer={isOrganizer}
          past={past}
          logged={logged}
          rsvped={rsvped}
          price={options.price}
          routes={{ ...routes, ...globalRoutes }}
          forUsers={forUsers}
        />
      )}
    </EventHeaderContainer>
  );
};

EventHeader.propTypes = {
  id: PropTypes.string,
  name: PropTypes.string,
  startTime: PropTypes.instanceOf(DateTime),
  endTime: PropTypes.instanceOf(DateTime),
  schedule: PropTypes.instanceOf(Interval),
  hasSubscriptionForm: PropTypes.bool,
  isOrganizer: PropTypes.bool,
  options: PropTypes.shape({
    price: PropTypes.string,
  }),
  rsvp: PropTypes.string,
  routes: PropTypes.object,
  forUsers: PropTypes.string,
  hasRightSubscription: PropTypes.bool,
};

export default EventHeader;
<|endoftext|>


<commit_before>/**
 * Created by truthlighting on 9/20/2016.
 */
module.exports =
{
    initSpawnQue: function()
    {
        if(Memory.spawnQue == undefined)
            Memory.spawnQue = [ ];
    },

    addToQue: function(creep, unshift)
    {
        this.initSpawnQue();

        if(unshift != undefined && unshift === true)
            Memory.spawnQue.unshift(creep);
        else
            Memory.spawnQue.push(creep);
    },

    spawnNextInQue: function()
    {

        this.initSpawnQue();

        if(!Memory.spawnQue.length)
            return;

        var spawns = Game.rooms['W2N48'].find(FIND_MY_SPAWNS, {
            filter: function(spawn)
            {
                return spawn.spawning === undefined || spawn.spawning === null;
            }
        });
        //console.log(spawns);

        if(!spawns.length)
            return;

        var role = Memory.spawnQue[0];

        if(typeof role == "string")
        {
            role = { type: role, memory: { } };
        }
        var body = [];
        var me = this;
        //console.log(spawns);
        var toSpawnAt = spawns.filter(function(spawn)
        {
            //console.log("spawn: " + spawn + " role.type: " + role.type);
            //console.log(me.canSpawn(spawn, role.type));
            body = me.canSpawn(spawn, role.type);
            return body;
        });
        //console.log("toSpawnAt: " + toSpawnAt);
        if(!toSpawnAt.length)
            return;
        //console.log("getting here1");
        toSpawnAt = toSpawnAt[0];
        this.spawn(role.type, role.memory, toSpawnAt, body);

        Memory.spawnQue.shift();
    },

    spawn: function(role, memory, spawnPoint)
    {
        console.log("role: " + role + " memory: " + memory + " spawnPoint: " + spawnPoint);
        if(!spawnPoint)
            spawnPoint = Game.spawns['Harmony'];

        var manager = require('roleManager');

        if(!manager.roleExists(role))
        {
            return;
        }
        /*if(!this.canSpawn(spawnPoint, role))
        {
            return;
        }*/

        if(memory == undefined)
            memory = { };

        memory['role'] = role;

        var nameCount = 0;
        var name = null;
        while(name == null)
        {
            nameCount++;
            var tryName = role + nameCount;
            if(Game.creeps[tryName] == undefined)
                name = tryName;
        }

        console.log('Spawning ' + role);
        console.log(spawnPoint.createCreep(manager.getRoleBodyParts(role), name, memory));
    },

    canSpawn: function(spawnPoint, role)
    {
        if(typeof spawnPoint == "string" && role == undefined)
        {
            role = spawnPoint;
            spawnPoint = Game.spawns['Harmony'];
        }
        //console.log(role);
        //console.log("spawnPoint: " + spawnPoint + " spawnPoint.energy: " + spawnPoint.energy + " this.room.energyAvailable: " + spawnPoint.room.energyAvailable);
        //return spawnPoint.energy >= this.spawnCost(role)
        return this.findMostExpensiveAffordableBody(spawnPoint, role)
            && (spawnPoint.spawning == null
            || spawnPoint.spawning == undefined);
    },

    findMostExpensiveAffordableBody: function(spawnPoint, role)
    {
        var manager = require('roleManager');
        var bodyArray = manager.getRoleBodyParts(role);

        for (var index in bodyArray)
        {
            var body = bodyArray[index];
            if (this.spawnCost(body) <= spawnPoint.room.energyAvailable)
                return body;
        }

    },

    spawnCost: function(body)
    {
       // var manager = require('roleManager');
       // var parts = manager.getRoleBodyParts(role);

        var total = 0;
        for(var index in body)
        {
            var bodyPart = body[index];
            switch(bodyPart)
            {
                case MOVE:
                    total += 50
                    break;

                case WORK:
                    total += 100
                    break;

                case CARRY:
                    total += 50
                    break;

                case ATTACK:
                    total += 80
                    break;

                case RANGED_ATTACK:
                    total += 150
                    break;

                case HEAL:
                    total += 250
                    break;

                case TOUGH:
                    total += 10
                    break;

                case CLAIM:
                    total += 600
            }
        }
        //console.log("body: " + body);
        //console.log("total cost: " + total);
        return total;
    },

    killAll: function(role)
    {
        for(var i in Game.creeps) {
            if(role == undefined || Game.creeps[i].memory.role == role)
                Game.creeps[i].suicide();
        }
    }
}<commit_msg>Fixing miner<commit_after>/**
 * Created by truthlighting on 9/20/2016.
 */
module.exports =
{
    initSpawnQue: function()
    {
        if(Memory.spawnQue == undefined)
            Memory.spawnQue = [ ];
    },

    addToQue: function(creep, unshift)
    {
        this.initSpawnQue();

        if(unshift != undefined && unshift === true)
            Memory.spawnQue.unshift(creep);
        else
            Memory.spawnQue.push(creep);
    },

    spawnNextInQue: function()
    {

        this.initSpawnQue();

        if(!Memory.spawnQue.length)
            return;

        var spawns = Game.rooms['W2N48'].find(FIND_MY_SPAWNS, {
            filter: function(spawn)
            {
                return spawn.spawning === undefined || spawn.spawning === null;
            }
        });
        //console.log(spawns);

        if(!spawns.length)
            return;

        var role = Memory.spawnQue[0];

        if(typeof role == "string")
        {
            role = { type: role, memory: { } };
        }
        var body = [];
        var me = this;
        //console.log(spawns);
        var toSpawnAt = spawns.filter(function(spawn)
        {
            //console.log("spawn: " + spawn + " role.type: " + role.type);
            //console.log(me.canSpawn(spawn, role.type));
            body = me.canSpawn(spawn, role.type);
            return body;
        });
        //console.log("toSpawnAt: " + toSpawnAt);
        if(!toSpawnAt.length)
            return;
        //console.log("getting here1");
        toSpawnAt = toSpawnAt[0];
        console.log("body before pass: " + body);
        this.spawn(role.type, role.memory, toSpawnAt, body);

        Memory.spawnQue.shift();
    },

    spawn: function(role, memory, spawnPoint, body)
    {
        console.log("role: " + role + " memory: " + memory + " spawnPoint: " + spawnPoint);
        if(!spawnPoint)
            spawnPoint = Game.spawns['Harmony'];

        var manager = require('roleManager');

        if(!manager.roleExists(role))
        {
            return;
        }
        /*if(!this.canSpawn(spawnPoint, role))
        {
            return;
        }*/

        if(memory == undefined)
            memory = { };

        memory['role'] = role;

        var nameCount = 0;
        var name = null;
        while(name == null)
        {
            nameCount++;
            var tryName = role + nameCount;
            if(Game.creeps[tryName] == undefined)
                name = tryName;
        }

        console.log('Spawning ' + role);
        console.log(spawnPoint.createCreep(body, name, memory));
    },

    canSpawn: function(spawnPoint, role)
    {
        if(typeof spawnPoint == "string" && role == undefined)
        {
            role = spawnPoint;
            spawnPoint = Game.spawns['Harmony'];
        }
        //console.log(role);
        //console.log("spawnPoint: " + spawnPoint + " spawnPoint.energy: " + spawnPoint.energy + " this.room.energyAvailable: " + spawnPoint.room.energyAvailable);
        //return spawnPoint.energy >= this.spawnCost(role)
        return this.findMostExpensiveAffordableBody(spawnPoint, role)
            && (spawnPoint.spawning == null
            || spawnPoint.spawning == undefined);
    },

    findMostExpensiveAffordableBody: function(spawnPoint, role)
    {
        var manager = require('roleManager');
        var bodyArray = manager.getRoleBodyParts(role);

        for (var index in bodyArray)
        {
            var body = bodyArray[index];
            if (this.spawnCost(body) <= spawnPoint.room.energyAvailable)
                return body;
        }

    },

    spawnCost: function(body)
    {
       // var manager = require('roleManager');
       // var parts = manager.getRoleBodyParts(role);

        var total = 0;
        for(var index in body)
        {
            var bodyPart = body[index];
            switch(bodyPart)
            {
                case MOVE:
                    total += 50
                    break;

                case WORK:
                    total += 100
                    break;

                case CARRY:
                    total += 50
                    break;

                case ATTACK:
                    total += 80
                    break;

                case RANGED_ATTACK:
                    total += 150
                    break;

                case HEAL:
                    total += 250
                    break;

                case TOUGH:
                    total += 10
                    break;

                case CLAIM:
                    total += 600
            }
        }
        //console.log("body: " + body);
        //console.log("total cost: " + total);
        return total;
    },

    killAll: function(role)
    {
        for(var i in Game.creeps) {
            if(role == undefined || Game.creeps[i].memory.role == role)
                Game.creeps[i].suicide();
        }
    }
}<|endoftext|>


<commit_before>import SPELLS from 'common/SPELLS';
import CoreAbilities from 'parser/core/modules/Abilities';
import ISSUE_IMPORTANCE from 'parser/core/ISSUE_IMPORTANCE';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational
      {
        spell: SPELLS.MORTAL_STRIKE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 6 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.OVERPOWER,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 12,
        charges: 1 + (combatant.hasTalent(SPELLS.DREADNAUGHT_TALENT.id) ? 1 : 0),
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.OVERPOWER.id,
      },
      {
        spell: SPELLS.SLAM,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REND_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.REND_TALENT.id),
      },
      {
        spell: SPELLS.COLOSSUS_SMASH,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: !combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id),
      },
      {
        spell: SPELLS.SKULLSPLITTER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 21 / (1 + haste),
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.SKULLSPLITTER_TALENT.id),
      },
      {
        spell: [SPELLS.EXECUTE, SPELLS.EXECUTE_GLYPHED],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      // Rotational AOE
      {
        spell: SPELLS.WARBREAKER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id), // replaces Colussus Smash
      },
      {
        spell: SPELLS.CLEAVE_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: haste => 9 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6, // I don't know at what efficiency this talent becomes worth it so I'm keeping it save
        },
        enabled: combatant.hasTalent(SPELLS.CLEAVE_TALENT.id),
      },
      {
        spell: SPELLS.SWEEPING_STRIKES,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.SWEEPING_STRIKES.id,
      },
      {
        spell: SPELLS.WHIRLWIND,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BLADESTORM,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: !combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id),
        buffSpellId: SPELLS.BLADESTORM.id,
      },
      {
        spell: SPELLS.RAVAGER_TALENT_ARMS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id), // Replaces Bladestorm
      },
      // Others
      {
        spell: SPELLS.VICTORY_RUSH,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id),
      },
      {
        spell: SPELLS.IMPENDING_VICTORY_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id), // Replaces Victory Rush
      },
      // Cooldowns
      {
        spell: SPELLS.AVATAR_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.AVATAR_TALENT.id),
      },
      {
        spell: SPELLS.DEADLY_CALM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.DEADLY_CALM_TALENT.id),
        buffSpellId: SPELLS.DEADLY_CALM_TALENT.id,
      },
      // Defensive
      {
        spell: SPELLS.DEFENSIVE_STANCE_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 6,
        gcd: null,
        enabled: combatant.hasTalent(SPELLS.DEFENSIVE_STANCE_TALENT.id),
        buffSpellId: SPELLS.DEFENSIVE_STANCE_TALENT.id,
      },
      {
        spell: SPELLS.DIE_BY_THE_SWORD,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: null,
        castEfficiency: {
          suggestion: false,
          recommendedEfficiency: 0.4,
        },
        buffSpellId: SPELLS.DIE_BY_THE_SWORD.id,
      },
      {
        spell: SPELLS.RALLYING_CRY,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
          extraSuggestion: 'Use it preemptively as a buffer against large AOE, or reactively if you notice your raid is getting dangerously low on health.',
        },
        buffSpellId: SPELLS.RALLYING_CRY_BUFF.id,
      },
      // Utility
      {
        spell: SPELLS.CHARGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 20 - (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 3 : 0),
        charges: 1 + (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 1 : 0),
        gcd: null, // Off gcd since 8.1
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion: 'Use it to get back into range after moving out to avoid mechanics. Not only does this allow you to get back to DPSing faster, it also generates rage for you to DPS with.',
        },
      },
      {
        spell: SPELLS.HEROIC_LEAP,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        buffSpellId: SPELLS.BOUNDING_STRIDE_BUFF.id,
        cooldown: 45 - (combatant.hasTalent(SPELLS.BOUNDING_STRIDE_TALENT.id) ? 15 : 0),
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion: 'Use it to escape mechanics at the last moment, allowing you more time to DPS.',
        },
      },
      {
        spell: SPELLS.STORM_BOLT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.4,
          extraSuggestion: 'If you\'re picking a utility talent over something that increases your mobility or survivability, you better use it.',
        },
        enabled: combatant.hasTalent(SPELLS.STORM_BOLT_TALENT.id),
      },
      {
        spell: SPELLS.PUMMEL,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: null,
      },
      {
        spell: SPELLS.BERSERKER_RAGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        gcd: null,
        buffSpellId: SPELLS.BERSERKER_RAGE.id,
      },
      {
        spell: SPELLS.HEROIC_THROW,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 6,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_SHOUT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HAMSTRING,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BATTLE_SHOUT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TAUNT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
        gcd: null,
      },
    ];
  }
}

export default Abilities;
<commit_msg>MS suggesitons<commit_after>import SPELLS from 'common/SPELLS';
import CoreAbilities from 'parser/core/modules/Abilities';
import ISSUE_IMPORTANCE from 'parser/core/ISSUE_IMPORTANCE';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational
      {
        spell: SPELLS.MORTAL_STRIKE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 6 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: false, // Suggestions are in MortalStrike.js
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.OVERPOWER,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 12,
        charges: 1 + (combatant.hasTalent(SPELLS.DREADNAUGHT_TALENT.id) ? 1 : 0),
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.OVERPOWER.id,
      },
      {
        spell: SPELLS.SLAM,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REND_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.REND_TALENT.id),
      },
      {
        spell: SPELLS.COLOSSUS_SMASH,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: !combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id),
      },
      {
        spell: SPELLS.SKULLSPLITTER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 21 / (1 + haste),
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.SKULLSPLITTER_TALENT.id),
      },
      {
        spell: [SPELLS.EXECUTE, SPELLS.EXECUTE_GLYPHED],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      // Rotational AOE
      {
        spell: SPELLS.WARBREAKER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id), // replaces Colussus Smash
      },
      {
        spell: SPELLS.CLEAVE_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: haste => 9 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6, // I don't know at what efficiency this talent becomes worth it so I'm keeping it save
        },
        enabled: combatant.hasTalent(SPELLS.CLEAVE_TALENT.id),
      },
      {
        spell: SPELLS.SWEEPING_STRIKES,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.SWEEPING_STRIKES.id,
      },
      {
        spell: SPELLS.WHIRLWIND,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BLADESTORM,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: !combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id),
        buffSpellId: SPELLS.BLADESTORM.id,
      },
      {
        spell: SPELLS.RAVAGER_TALENT_ARMS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id), // Replaces Bladestorm
      },
      // Others
      {
        spell: SPELLS.VICTORY_RUSH,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id),
      },
      {
        spell: SPELLS.IMPENDING_VICTORY_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id), // Replaces Victory Rush
      },
      // Cooldowns
      {
        spell: SPELLS.AVATAR_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.AVATAR_TALENT.id),
      },
      {
        spell: SPELLS.DEADLY_CALM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.DEADLY_CALM_TALENT.id),
        buffSpellId: SPELLS.DEADLY_CALM_TALENT.id,
      },
      // Defensive
      {
        spell: SPELLS.DEFENSIVE_STANCE_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 6,
        gcd: null,
        enabled: combatant.hasTalent(SPELLS.DEFENSIVE_STANCE_TALENT.id),
        buffSpellId: SPELLS.DEFENSIVE_STANCE_TALENT.id,
      },
      {
        spell: SPELLS.DIE_BY_THE_SWORD,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: null,
        castEfficiency: {
          suggestion: false,
          recommendedEfficiency: 0.4,
        },
        buffSpellId: SPELLS.DIE_BY_THE_SWORD.id,
      },
      {
        spell: SPELLS.RALLYING_CRY,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
          extraSuggestion: 'Use it preemptively as a buffer against large AOE, or reactively if you notice your raid is getting dangerously low on health.',
        },
        buffSpellId: SPELLS.RALLYING_CRY_BUFF.id,
      },
      // Utility
      {
        spell: SPELLS.CHARGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 20 - (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 3 : 0),
        charges: 1 + (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 1 : 0),
        gcd: null, // Off gcd since 8.1
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion: 'Use it to get back into range after moving out to avoid mechanics. Not only does this allow you to get back to DPSing faster, it also generates rage for you to DPS with.',
        },
      },
      {
        spell: SPELLS.HEROIC_LEAP,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        buffSpellId: SPELLS.BOUNDING_STRIDE_BUFF.id,
        cooldown: 45 - (combatant.hasTalent(SPELLS.BOUNDING_STRIDE_TALENT.id) ? 15 : 0),
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion: 'Use it to escape mechanics at the last moment, allowing you more time to DPS.',
        },
      },
      {
        spell: SPELLS.STORM_BOLT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.4,
          extraSuggestion: 'If you\'re picking a utility talent over something that increases your mobility or survivability, you better use it.',
        },
        enabled: combatant.hasTalent(SPELLS.STORM_BOLT_TALENT.id),
      },
      {
        spell: SPELLS.PUMMEL,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: null,
      },
      {
        spell: SPELLS.BERSERKER_RAGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        gcd: null,
        buffSpellId: SPELLS.BERSERKER_RAGE.id,
      },
      {
        spell: SPELLS.HEROIC_THROW,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 6,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_SHOUT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HAMSTRING,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BATTLE_SHOUT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TAUNT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
        gcd: null,
      },
    ];
  }
}

export default Abilities;
<|endoftext|>


<commit_before>var fs = require('fs');

exports.compareOutput = function (test) {
	test.expect(2);

	var input = fs.readFileSync('tmp/index.html', { encoding: 'utf8' });
	var expected = fs.readFileSync('test/expected/index.html', { encoding: 'utf8' });

	test.equal(input, expected, "Output doesn't match expected output.");

  // Test overwriting files
  input = fs.readFileSync('tmp/overwrite.html', { encoding: 'utf8' });
  expected = fs.readFileSync('test/expected/overwrite.html', { encoding: 'utf8' });

  test.equal(input, expected, "Output doesn't match expected output.");

	test.done();
};
<commit_msg>Fix indentation in html test file.<commit_after>var fs = require('fs');

exports.compareOutput = function (test) {
	test.expect(2);

	var input = fs.readFileSync('tmp/index.html', { encoding: 'utf8' });
	var expected = fs.readFileSync('test/expected/index.html', { encoding: 'utf8' });

	// Test overwriting files
	input = fs.readFileSync('tmp/overwrite.html', { encoding: 'utf8' });
	expected = fs.readFileSync('test/expected/overwrite.html', { encoding: 'utf8' });

	test.equal(input, expected, "Output doesn't match expected output.");

	test.equal(input, expected, "Output doesn't match expected output.");
	test.done();
};<|endoftext|>


<commit_before>var Bulk = require('../index');
var expect = require('chai').expect;

describe('The neo4j-bulk interface', function() {
	it('should be an instantiable class', function () {
		var bulk = new Bulk();
		expect(bulk).to.be.an('object');
	});

	describe('instances', function () {
		it('instances should have a method findNodeID', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('findNodeID')
				.that.is.a('function');
		});

		it('should have a method addNode', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addNode')
				.that.is.a('function');
		});

		it('should have a method addLabel', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addLabel')
				.that.is.a('function');
		});

		it('should have a method addRelation', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addRelation')
				.that.is.a('function');
		});

		it('should have a method branch', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('branch')
				.that.is.a('function');
		});

		it('should have a method generateBatch', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('generateBatch')
				.that.is.a('function');
		});
	});
});<commit_msg>removed legacy test code<commit_after>var Bulk = require('../index');
var expect = require('chai').expect;

describe('The neo4j-bulk interface', function() {
	it('should be an instantiable class', function () {
		var bulk = new Bulk();
		expect(bulk).to.be.an('object');
	});

	describe('instances', function () {
		it('should have a method addNode', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addNode')
				.that.is.a('function');
		});

		it('should have a method addLabel', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addLabel')
				.that.is.a('function');
		});

		it('should have a method addRelation', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('addRelation')
				.that.is.a('function');
		});

		it('should have a method branch', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('branch')
				.that.is.a('function');
		});

		it('should have a method generateBatch', function () {
			var bulk = new Bulk();
			expect(bulk).to.be.an('object')
				.that.has.a.property('generateBatch')
				.that.is.a('function');
		});
	});
});<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var jade = require('jade');

module.exports = {
    'version': function(assert){
        assert.ok(/^\d+\.\d+\.\d+$/.test(jade.version), "Invalid version format");
    }
};<commit_msg>Started tests<commit_after>
/**
 * Module dependencies.
 */

var jade = require('jade');

// Shortcut

var render = jade.render;

module.exports = {
    'version': function(assert){
        assert.ok(/^\d+\.\d+\.\d+$/.test(jade.version), "Invalid version format");
    },

    'test tags': function(assert){
        var str = [
            'p',
            'div',
            'img'
        ].join('\n');

        var html = [
            '

',
            '

',
            ''
        ].join('');

        assert.equal(html, render(str));
    },

    'test attrs': function(assert){
        assert.equal('', render('img(src="


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import DeviceInfo from 'react-native-device-info';
import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from './actions';

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const MIN_SYNC_BATCH_SIZE = 10;
const MAX_SYNC_BATCH_SIZE = 500;
const OPTIMAL_BATCH_SPEED = 5;
const uniqueId = DeviceInfo.getUniqueID();

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    this.refreshSyncParams();
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));
  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));
  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));
  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));
  setError = errorMessage => this.dispatch(setSyncError(errorMessage));
  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));
  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  refreshSyncParams = () => {
    this.serverURL = this.settings.get(SYNC_URL);
    this.thisSiteId = this.settings.get(SYNC_SITE_ID);
    this.serverId = this.settings.get(SYNC_SERVER_ID);
    this.thisSiteName = this.settings.get(SYNC_SITE_NAME);
    this.authHeader = this.authenticator.getAuthHeader();
    this.batchSize = MIN_SYNC_BATCH_SIZE;
  };

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh. Do the same for syncSiteName, so that it isn't possible to start syncing
    // data from a site different to what initialisation was previously started with.
    const oldSyncURL = this.serverURL;
    const oldSyncSiteName = this.thisSiteName;
    const isFresh =
      !oldSyncURL || serverURL !== oldSyncURL || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) {
      this.database.write(() => {
        this.database.deleteAll();
      });
    }
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      this.refreshSyncParams(); // authenticate sets all the sync settings in database, so refresh

      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${this.serverURL}/sync/v3/initial_dump/` +
            `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
          {
            headers: {
              Authorization: this.authHeader,
              msupply_site_uuid: uniqueId,
            },
          },
        )
          .then(response => response.json()
            .then(responseJson => {
              if (responseJson.error) throw new Error(responseJson.error);
            }));
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }

    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.setIsSyncing(false);
  };

  /**
   * Return whether the synchroniser has been initialised.
   * @return {boolean} True if initial sync has been completed successfully
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed
   * @return {boolean} 'true' if the last call of synchronise failed
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {none}
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);
      this.refreshSyncParams();

      // Keeps track between app close/open whether last sync was successful
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here means that any errors thrown by push or pull will be caught by the
      // outer try/catch
      await this.push();
      await this.pull();

      // Store persistent sync details in settings
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  push = async () => {
    this.setProgressMessage('Pushing changes to the server');
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const batchComplete = this.batchSizeAdjustor();
      const recordsToSync = this.syncQueue.next(this.batchSize);
      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Apply map function to get translated records
        .filter(record => !!record); // If error thrown, may be null so filter falsy values out
      await this.pushRecords(translatedRecords);
      // Records that threw errors in translateRecord() are still removed
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
      batchComplete();
    }
  };

  /**
   * Handles translating records for push() and catching errors
   * @param {object} record The outgoing record to be translated
   * @return {object} The translated record output
   */
  translateRecord = record => {
    try {
      // Try translate the record
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync throw it again to pass it up to next handler
      // See outgoingSyncUtils.js
      if (!error.canDeleteSyncOut) throw error;
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  pushRecords = async records => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          msupply_site_uuid: uniqueId,
        },
        body: JSON.stringify(records),
      },
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      if (responseJson.error.startsWith('Site registration doesn\'t match.')) {
        throw new Error(responseJson.error);
      } else {
        throw new Error('Server rejected pushed records');
      }
    }
  };

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  pull = async () => {
    let waitingRecordCount = await this.getWaitingRecordCount();
    let total = waitingRecordCount;
    let progress = 0;

    this.setProgressMessage('Pulling changes from the server');
    this.setProgress(progress);
    this.setTotal(total);

    // Pull this.batchSize amount of records at a time from server
    while (progress < total) {
      const batchComplete = this.batchSizeAdjustor();
      // Get a batch of records and integrate them
      const incomingRecords = await this.getIncomingRecords();
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(incomingRecords);
      // Break out if incoming records is actually empty (something weird happened on server API).
      // Basically future proof to not get infinite loop.
      if (incomingRecords.length <= 0) break;
      progress += incomingRecords.length;

      // Check no new records added to incoming queue on last iteration.
      if (progress >= total) {
        waitingRecordCount = await this.getWaitingRecordCount();
        if (waitingRecordCount > 0) {
          total = progress + waitingRecordCount;
          this.setTotal(total);
        }
      }

      this.incrementProgress(incomingRecords.length);
      batchComplete();
    }
  };

  /**
   * Returns the number of records left to pull
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  getWaitingRecordCount = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/count` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        headers: {
          Authorization: this.authHeader,
          msupply_site_uuid: uniqueId,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  getIncomingRecords = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}&limit=${this.batchSize}`,
      {
        headers: {
          Authorization: this.authHeader,
          msupply_site_uuid: uniqueId,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {object} syncJson  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  acknowledgeRecords = async records => {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${this.serverURL}/sync/v3/acknowledged_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          msupply_site_uuid: uniqueId,
        },
        body: JSON.stringify(requestBody),
      },
    )
      .then(response => response.json()
        .then(responseJson => {
          if (responseJson.error) throw new Error(responseJson.error);
        }));
  };

  /**
   * Closure for handling adjusting the batch size that sync uses
   * @return {function} A closure function that when called ends timing and adjusts batch size
   */
  batchSizeAdjustor = () => {
    const start = Date.now();
    return () => {
      const timeTaken = (Date.now() - start) / 1000;
      const durationPerRecord = timeTaken / this.batchSize;
      const optimalBatchSize = OPTIMAL_BATCH_SPEED / durationPerRecord;
      let newBatchSize = optimalBatchSize;

      newBatchSize = Math.floor(newBatchSize);
      newBatchSize = Math.max(newBatchSize, MIN_SYNC_BATCH_SIZE);
      newBatchSize = Math.min(newBatchSize, MAX_SYNC_BATCH_SIZE);

      this.batchSize = newBatchSize;
    };
  };
}
<commit_msg>#762 Replaced underscore with hyphen in the sync request HTTP header<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import DeviceInfo from 'react-native-device-info';
import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from './actions';

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const MIN_SYNC_BATCH_SIZE = 10;
const MAX_SYNC_BATCH_SIZE = 500;
const OPTIMAL_BATCH_SPEED = 5;
const uniqueId = DeviceInfo.getUniqueID();

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    this.refreshSyncParams();
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));
  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));
  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));
  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));
  setError = errorMessage => this.dispatch(setSyncError(errorMessage));
  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));
  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  refreshSyncParams = () => {
    this.serverURL = this.settings.get(SYNC_URL);
    this.thisSiteId = this.settings.get(SYNC_SITE_ID);
    this.serverId = this.settings.get(SYNC_SERVER_ID);
    this.thisSiteName = this.settings.get(SYNC_SITE_NAME);
    this.authHeader = this.authenticator.getAuthHeader();
    this.batchSize = MIN_SYNC_BATCH_SIZE;
  };

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh. Do the same for syncSiteName, so that it isn't possible to start syncing
    // data from a site different to what initialisation was previously started with.
    const oldSyncURL = this.serverURL;
    const oldSyncSiteName = this.thisSiteName;
    const isFresh =
      !oldSyncURL || serverURL !== oldSyncURL || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) {
      this.database.write(() => {
        this.database.deleteAll();
      });
    }
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      this.refreshSyncParams(); // authenticate sets all the sync settings in database, so refresh

      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${this.serverURL}/sync/v3/initial_dump/` +
            `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
          {
            headers: {
              Authorization: this.authHeader,
              'msupply-site-uuid': uniqueId,
            },
          },
        )
          .then(response => response.json()
            .then(responseJson => {
              if (responseJson.error) throw new Error(responseJson.error);
            }));
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }

    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.setIsSyncing(false);
  };

  /**
   * Return whether the synchroniser has been initialised.
   * @return {boolean} True if initial sync has been completed successfully
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed
   * @return {boolean} 'true' if the last call of synchronise failed
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {none}
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);
      this.refreshSyncParams();

      // Keeps track between app close/open whether last sync was successful
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here means that any errors thrown by push or pull will be caught by the
      // outer try/catch
      await this.push();
      await this.pull();

      // Store persistent sync details in settings
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  push = async () => {
    this.setProgressMessage('Pushing changes to the server');
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const batchComplete = this.batchSizeAdjustor();
      const recordsToSync = this.syncQueue.next(this.batchSize);
      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Apply map function to get translated records
        .filter(record => !!record); // If error thrown, may be null so filter falsy values out
      await this.pushRecords(translatedRecords);
      // Records that threw errors in translateRecord() are still removed
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
      batchComplete();
    }
  };

  /**
   * Handles translating records for push() and catching errors
   * @param {object} record The outgoing record to be translated
   * @return {object} The translated record output
   */
  translateRecord = record => {
    try {
      // Try translate the record
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync throw it again to pass it up to next handler
      // See outgoingSyncUtils.js
      if (!error.canDeleteSyncOut) throw error;
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  pushRecords = async records => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          'msupply-site-uuid': uniqueId,
        },
        body: JSON.stringify(records),
      },
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      if (responseJson.error.startsWith('Site registration doesn\'t match.')) {
        throw new Error(responseJson.error);
      } else {
        throw new Error('Server rejected pushed records');
      }
    }
  };

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  pull = async () => {
    let waitingRecordCount = await this.getWaitingRecordCount();
    let total = waitingRecordCount;
    let progress = 0;

    this.setProgressMessage('Pulling changes from the server');
    this.setProgress(progress);
    this.setTotal(total);

    // Pull this.batchSize amount of records at a time from server
    while (progress < total) {
      const batchComplete = this.batchSizeAdjustor();
      // Get a batch of records and integrate them
      const incomingRecords = await this.getIncomingRecords();
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(incomingRecords);
      // Break out if incoming records is actually empty (something weird happened on server API).
      // Basically future proof to not get infinite loop.
      if (incomingRecords.length <= 0) break;
      progress += incomingRecords.length;

      // Check no new records added to incoming queue on last iteration.
      if (progress >= total) {
        waitingRecordCount = await this.getWaitingRecordCount();
        if (waitingRecordCount > 0) {
          total = progress + waitingRecordCount;
          this.setTotal(total);
        }
      }

      this.incrementProgress(incomingRecords.length);
      batchComplete();
    }
  };

  /**
   * Returns the number of records left to pull
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  getWaitingRecordCount = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/count` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        headers: {
          Authorization: this.authHeader,
          'msupply-site-uuid': uniqueId,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  getIncomingRecords = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}&limit=${this.batchSize}`,
      {
        headers: {
          Authorization: this.authHeader,
          'msupply-site-uuid': uniqueId,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {object} syncJson  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  acknowledgeRecords = async records => {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${this.serverURL}/sync/v3/acknowledged_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          'msupply-site-uuid': uniqueId,
        },
        body: JSON.stringify(requestBody),
      },
    )
      .then(response => response.json()
        .then(responseJson => {
          if (responseJson.error) throw new Error(responseJson.error);
        }));
  };

  /**
   * Closure for handling adjusting the batch size that sync uses
   * @return {function} A closure function that when called ends timing and adjusts batch size
   */
  batchSizeAdjustor = () => {
    const start = Date.now();
    return () => {
      const timeTaken = (Date.now() - start) / 1000;
      const durationPerRecord = timeTaken / this.batchSize;
      const optimalBatchSize = OPTIMAL_BATCH_SPEED / durationPerRecord;
      let newBatchSize = optimalBatchSize;

      newBatchSize = Math.floor(newBatchSize);
      newBatchSize = Math.max(newBatchSize, MIN_SYNC_BATCH_SIZE);
      newBatchSize = Math.min(newBatchSize, MAX_SYNC_BATCH_SIZE);

      this.batchSize = newBatchSize;
    };
  };
}
<|endoftext|>


<commit_before>(function() {

    window.addEventListener('load', function() {
        if (typeof window.iframe === 'undefined') {
            console.log('hmm');
            window.iframe = document.createElement('iframe');
            iframe.setAttribute('src',         '../vendor/jqtouch/demos/main/index.html');
            iframe.setAttribute('id',          'app');
            iframe.setAttribute('name',        'app');
            iframe.setAttribute('width',       '320');
            iframe.setAttribute('height',      '480');
            iframe.setAttribute('scrolling',   'auto');
            iframe.setAttribute('frameborder', '0');
            // iframe.setAttribute('style', 'visibility:hidden;position:absolute;top:0px;left:0px;z-index:-1;');
        }

        window.iframe.onload = function() {
            var e = document.createEvent('Events');
            e.initEvent('appready');
            document.dispatchEvent(e);
        };

        document.body.appendChild(iframe);
    }, false);

    document.addEventListener('appready', function() {
        document.removeEventListener('appready', arguments.callee, false);

        dominator.config.window = document.getElementById('app').contentWindow;
        dominator.test();
    }, false);

})();
<commit_msg>Remove console.log('hmmm');<commit_after>(function() {

    window.addEventListener('load', function() {
        if (typeof window.iframe === 'undefined') {
            window.iframe = document.createElement('iframe');
            iframe.setAttribute('src',         '../vendor/jqtouch/demos/main/index.html');
            iframe.setAttribute('id',          'app');
            iframe.setAttribute('name',        'app');
            iframe.setAttribute('width',       '320');
            iframe.setAttribute('height',      '480');
            iframe.setAttribute('scrolling',   'auto');
            iframe.setAttribute('frameborder', '0');
            // iframe.setAttribute('style', 'visibility:hidden;position:absolute;top:0px;left:0px;z-index:-1;');
        }

        window.iframe.onload = function() {
            var e = document.createEvent('Events');
            e.initEvent('appready');
            document.dispatchEvent(e);
        };

        document.body.appendChild(iframe);
    }, false);

    document.addEventListener('appready', function() {
        document.removeEventListener('appready', arguments.callee, false);

        dominator.config.window = document.getElementById('app').contentWindow;
        dominator.test();
    }, false);

})();
<|endoftext|>


<commit_before>var assert = require('assert'),
    fs = require('fs'),
    glob = require('glob'),
    lumbar = require('../../lib/lumbar'),
    path = require('path'),
    wrench = require('wrench');

var counter = 0;
try { fs.mkdirSync('/tmp/lumbar-test', 0755); } catch (err) {}

exports.testDir = function(testName, configFile) {
  var outdir = '/tmp/lumbar-test/' + testName + '-' + path.basename(configFile) + '-' + Date.now() + '-' + (counter++);
  fs.mkdirSync(outdir, 0755);
  return outdir;
};
exports.assertExpected = function(outdir, expectedDir, configFile) {
  var expectedFiles = glob.globSync(expectedDir + '/**/*.*').map(function(fileName) {
        return fileName.substring(expectedDir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort(),
      generatedFiles = glob.globSync(outdir + '/**/*.*').map(function(fileName) {
        return fileName.substring(outdir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort();
  assert.deepEqual(generatedFiles, expectedFiles, configFile + ': file list matches' + JSON.stringify(expectedFiles) + JSON.stringify(generatedFiles));

  generatedFiles.forEach(function(fileName) {
    var generatedContent = fs.readFileSync(outdir + fileName, 'utf8'),
        expectedContent = fs.readFileSync(expectedDir + fileName, 'utf8');
    assert.equal(generatedContent, expectedContent, configFile + ':' + fileName + ': content matches');
  });
};

exports.runTest = function(configFile, expectedDir, options) {
  return function(done) {
    var outdir = exports.testDir('lumbar', configFile);
    this.title += ' ' + outdir;

    options = options || {};
    options.outdir = outdir;

    var expectedFiles = glob.globSync(expectedDir + '/**/*.{css,js}').map(function(fileName) {
          return fileName.substring(expectedDir.length);
        }).sort(),
        seenFiles = [];

    var arise = lumbar.init(configFile, options || {outdir: outdir});
    arise.on('output', function(status) {
      var statusFile = status.fileName.substring(outdir.length);
      if (!expectedFiles.some(function(fileName) { return statusFile === fileName; })) {
        assert.fail(undefined, status.fileName, configFile + ':' + statusFile + ': missing from expected list');
      } else {
        seenFiles.push(statusFile);
      }
      if (seenFiles.length < expectedFiles.length) {
        return;
      }

      exports.assertExpected(outdir, expectedDir, configFile);

      seenFiles = seenFiles.sort();
      assert.deepEqual(seenFiles, expectedFiles, 'seen file list matches');

      // Cleanup (Do cleanup here so the files remain for the failure case)
      wrench.rmdirSyncRecursive(outdir);

      done();
    });
    arise.build(undefined, function(err) {
      throw err;
    });
  };
}
<commit_msg>Add lumbar test build callback expectation<commit_after>var assert = require('assert'),
    fs = require('fs'),
    glob = require('glob'),
    lumbar = require('../../lib/lumbar'),
    path = require('path'),
    wrench = require('wrench');

var counter = 0;
try { fs.mkdirSync('/tmp/lumbar-test', 0755); } catch (err) {}

exports.testDir = function(testName, configFile) {
  var outdir = '/tmp/lumbar-test/' + testName + '-' + path.basename(configFile) + '-' + Date.now() + '-' + (counter++);
  fs.mkdirSync(outdir, 0755);
  return outdir;
};
exports.assertExpected = function(outdir, expectedDir, configFile) {
  var expectedFiles = glob.globSync(expectedDir + '/**/*.*').map(function(fileName) {
        return fileName.substring(expectedDir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort(),
      generatedFiles = glob.globSync(outdir + '/**/*.*').map(function(fileName) {
        return fileName.substring(outdir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort();
  assert.deepEqual(generatedFiles, expectedFiles, configFile + ': file list matches' + JSON.stringify(expectedFiles) + JSON.stringify(generatedFiles));

  generatedFiles.forEach(function(fileName) {
    var generatedContent = fs.readFileSync(outdir + fileName, 'utf8'),
        expectedContent = fs.readFileSync(expectedDir + fileName, 'utf8');
    assert.equal(generatedContent, expectedContent, configFile + ':' + fileName + ': content matches');
  });
};

exports.runTest = function(configFile, expectedDir, options) {
  return function(done) {
    var outdir = exports.testDir('lumbar', configFile);
    this.title += ' ' + outdir;

    options = options || {};
    options.outdir = outdir;

    var expectedFiles = glob.globSync(expectedDir + '/**/*.{css,js}').map(function(fileName) {
          return fileName.substring(expectedDir.length);
        }).sort(),
        seenFiles = [];

    var arise = lumbar.init(configFile, options || {outdir: outdir});
    arise.on('output', function(status) {
      var statusFile = status.fileName.substring(outdir.length);
      if (!expectedFiles.some(function(fileName) { return statusFile === fileName; })) {
        assert.fail(undefined, status.fileName, configFile + ':' + statusFile + ': missing from expected list');
      } else {
        seenFiles.push(statusFile);
      }
      if (seenFiles.length < expectedFiles.length) {
        return;
      }

      exports.assertExpected(outdir, expectedDir, configFile);

      seenFiles = seenFiles.sort();
      assert.deepEqual(seenFiles, expectedFiles, 'seen file list matches');

      // Cleanup (Do cleanup here so the files remain for the failure case)
      wrench.rmdirSyncRecursive(outdir);

      done();
    });
    var retCount = 0;
    arise.build(undefined, function(err) {
      retCount++;
      if (retCount > 1) {
        throw new Error('Build callback executed multiple times');
      }

      throw err;
    });
  };
}
<|endoftext|>


<commit_before>var _ = require('underscore'),
    assert = require('assert'),
    build = require('../../lib/build'),
    Config = require('../../lib/config'),
    Context = require('../../lib/context'),
    fs = require('fs'),
    glob = require('glob'),
    lumbar = require('../../lib/lumbar'),
    Mixins = require('../../lib/mixins'),
    path = require('path'),
    wrench = require('wrench');

var counter = 0;
try { fs.mkdirSync('/tmp/lumbar-test', 0755); } catch (err) {}

exports.testDir = function(testName, configFile) {
  var outdir = '/tmp/lumbar-test/' + testName + '-' + path.basename(configFile) + '-' + Date.now() + '-' + (counter++);
  fs.mkdirSync(outdir, 0755);
  return outdir;
};
exports.assertExpected = function(outdir, expectedDir, configFile) {
  var expectedFiles = glob.sync(expectedDir + '/**/*.*').map(function(fileName) {
        return fileName.substring(expectedDir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort(),
      generatedFiles = glob.sync(outdir + '/**/*.*').map(function(fileName) {
        return fileName.substring(outdir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort();
  assert.deepEqual(generatedFiles, expectedFiles, configFile + ': file list matches' + JSON.stringify(expectedFiles) + JSON.stringify(generatedFiles));

  generatedFiles.forEach(function(fileName) {
    var generatedStat = fs.statSync(outdir + fileName),
        expectedStat = fs.statSync(expectedDir + fileName);

    assert.equal(generatedStat.isFile(), expectedStat.isFile());
    assert.equal(generatedStat.isDirectory(), expectedStat.isDirectory());

    if (generatedStat.isDirectory()) {
      return;
    }

    var generatedContent = fs.readFileSync(outdir + fileName, 'utf8'),
        expectedContent = fs.readFileSync(expectedDir + fileName, 'utf8');
    assert.equal(generatedContent, expectedContent, configFile + ':' + fileName + ': content matches');
  });
};

exports.runTest = function(configFile, expectedDir, options, expectGlob) {
  return function(done) {
    var outdir = exports.testDir('lumbar', configFile);
    this.title += ' ' + outdir;

    options = options || {};
    options.outdir = outdir;

    var expectedFiles = glob.sync(expectedDir + (expectGlob || '/**/*.{js,css}')).map(function(fileName) {
          return fileName.substring(expectedDir.length);
        }).sort(),
        seenFiles = [];

    var arise = lumbar.init(configFile, options || {outdir: outdir});
    arise.on('output', function(status) {
      if (retCount > 0) {
        throw new Error('Output event seen after callback');
      }

      var statusFile = status.fileName.substring(outdir.length);
      if (!expectedFiles.some(function(fileName) { return statusFile === fileName; })) {
        assert.fail(undefined, status.fileName, configFile + ':' + statusFile + ': missing from expected list');
      } else {
        seenFiles.push(statusFile);
      }
      if (seenFiles.length < expectedFiles.length) {
        return;
      }

      exports.assertExpected(outdir, expectedDir, configFile);

      seenFiles = seenFiles.sort();
      assert.deepEqual(seenFiles, expectedFiles, 'seen file list matches');

      // Cleanup (Do cleanup here so the files remain for the failure case)
      wrench.rmdirSyncRecursive(outdir);

      done();
    });
    var retCount = 0;
    arise.build(undefined, function(err) {
      if (err) {
        throw err;
      }

      retCount++;
      if (retCount > 1) {
        throw new Error('Build callback executed multiple times');
      }
    });
  };
};

exports.mixinExec = function(module, mixins, config, callback) {
  var plugin = require('../../lib/plugin').create({});
  plugin.initialize({ attributes: {} });

  if (_.isFunction(config)) {
    callback = config;
    config = undefined;
  }

  config = Config.create(_.defaults({modules: {module: module}}, config));
  var context = new Context({module: module}, config, plugin, new Mixins({mixins: mixins}));
  context.options = {};

  context.mixins.initialize(context, function(err) {
    if (err) {
      throw err;
    }

    plugin.loadConfig(context, function(err) {
      if (err) {
        throw err;
      }

      callback(context.mixins, context);
    });
  });
};
<commit_msg>Use should rather than assert in lib<commit_after>var _ = require('underscore'),
    assert = require('assert'),
    build = require('../../lib/build'),
    Config = require('../../lib/config'),
    Context = require('../../lib/context'),
    fs = require('fs'),
    glob = require('glob'),
    lumbar = require('../../lib/lumbar'),
    Mixins = require('../../lib/mixins'),
    path = require('path'),
    should = require('should'),
    wrench = require('wrench');

var counter = 0;
try { fs.mkdirSync('/tmp/lumbar-test', 0755); } catch (err) {}

exports.testDir = function(testName, configFile) {
  var outdir = '/tmp/lumbar-test/' + testName + '-' + path.basename(configFile) + '-' + Date.now() + '-' + (counter++);
  fs.mkdirSync(outdir, 0755);
  return outdir;
};
exports.assertExpected = function(outdir, expectedDir, configFile) {
  var expectedFiles = glob.sync(expectedDir + '/**/*.*').map(function(fileName) {
        return fileName.substring(expectedDir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort(),
      generatedFiles = glob.sync(outdir + '/**/*.*').map(function(fileName) {
        return fileName.substring(outdir.length);
      }).filter(function(file) { return !/\/$/.test(file); }).sort();
  generatedFiles.should.eql(expectedFiles, configFile + ': file list matches' + JSON.stringify(expectedFiles) + JSON.stringify(generatedFiles));

  generatedFiles.forEach(function(fileName) {
    var generatedStat = fs.statSync(outdir + fileName),
        expectedStat = fs.statSync(expectedDir + fileName);

    generatedStat.isFile().should.eql(expectedStat.isFile());
    generatedStat.isDirectory().should.eql(expectedStat.isDirectory());

    if (generatedStat.isDirectory()) {
      return;
    }

    var generatedContent = fs.readFileSync(outdir + fileName, 'utf8'),
        expectedContent = fs.readFileSync(expectedDir + fileName, 'utf8');
    generatedContent.should.eql(expectedContent, configFile + ':' + fileName + ': content matches');
  });
};

exports.runTest = function(configFile, expectedDir, options, expectGlob) {
  return function(done) {
    var outdir = exports.testDir('lumbar', configFile);
    this.title += ' ' + outdir;

    options = options || {};
    options.outdir = outdir;

    var expectedFiles = glob.sync(expectedDir + (expectGlob || '/**/*.{js,css}')).map(function(fileName) {
          return fileName.substring(expectedDir.length);
        }).sort(),
        seenFiles = [];

    var arise = lumbar.init(configFile, options || {outdir: outdir});
    arise.on('output', function(status) {
      if (retCount > 0) {
        throw new Error('Output event seen after callback');
      }

      var statusFile = status.fileName.substring(outdir.length);
      if (!expectedFiles.some(function(fileName) { return statusFile === fileName; })) {
        assert.fail(undefined, status.fileName, configFile + ':' + statusFile + ': missing from expected list');
      } else {
        seenFiles.push(statusFile);
      }
      if (seenFiles.length < expectedFiles.length) {
        return;
      }

      exports.assertExpected(outdir, expectedDir, configFile);

      seenFiles = seenFiles.sort();
      seenFiles.should.eql(expectedFiles, 'seen file list matches');

      // Cleanup (Do cleanup here so the files remain for the failure case)
      wrench.rmdirSyncRecursive(outdir);

      done();
    });
    var retCount = 0;
    arise.build(undefined, function(err) {
      if (err) {
        throw err;
      }

      retCount++;
      if (retCount > 1) {
        throw new Error('Build callback executed multiple times');
      }
    });
  };
};

exports.mixinExec = function(module, mixins, config, callback) {
  var plugin = require('../../lib/plugin').create({});
  plugin.initialize({ attributes: {} });

  if (_.isFunction(config)) {
    callback = config;
    config = undefined;
  }

  config = Config.create(_.defaults({modules: {module: module}}, config));
  var context = new Context({module: module}, config, plugin, new Mixins({mixins: mixins}));
  context.options = {};

  context.mixins.initialize(context, function(err) {
    if (err) {
      throw err;
    }

    plugin.loadConfig(context, function(err) {
      if (err) {
        throw err;
      }

      callback(context.mixins, context);
    });
  });
};
<|endoftext|>