<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import { Container, Divider, Avatar, Link} from '@material-ui/core/';
import Typography from '../Typography';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// images

const styles = (theme) => ({
    root: {
        display: 'flex',
        overflow: 'hidden',
        backgroundColor: theme.palette.primary.light,
    },
    container: {
        marginTop: theme.spacing(15),
        marginBottom: theme.spacing(10),
        display: 'flex',
        position: 'relative',
    },
    more: {
        marginTop: theme.spacing(5),
    },
    item: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'left',
        padding: theme.spacing(0, 5),
    },
    avatarSize: {
        width: theme.spacing(15),
        height: theme.spacing(15),
    },
    icon: {
        alignItems: 'left',
        padding: theme.spacing(1),
        color: theme.palette.primary.dark,
    },
    card: {
        height: '20px',
        marginTop: theme.spacing(1),
        marginBottom: theme.spacing(8),
    },
    divider: {
        backgroundColor: theme.palette.secondary.dark,
        margin: theme.spacing(2, 2),
        // flexItem: true,
        display: "flex",
        width:"150vh",
        height: "1px",
    },
    title: {
        marginBottom: theme.spacing(3),
        marginTop: theme.spacing(3),
        color: theme.palette.secondary,
    }
});

const advisoryBoard = [
    {
        image: require('./../../assets/img/team/MakindeAdeagbo.jpeg'),
        name: 'Makinde Adeagbo',
        link: 'https://www.linkedin.com/in/makinde/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/TimnitGebru.jpg'),
        name: 'Timnit Gebru',
        link: 'https://ai.stanford.edu/~tgebru/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/HabenGirma.jpg'),
        name: 'Haben Girma',
        link: 'https://www.linkedin.com/in/habengirma/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/AyannaHoward.jpg'),
        name: 'Ayanna Howard',
        link: 'https://www.linkedin.com/in/ayanna-howard/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/CharlesIsbell.jpg'),
        name: 'Charles Isbell',
        link: 'https://www.linkedin.com/in/charles-isbell-a12b808/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/BrandeisMarshall.jpg'),
        name: 'Brandeis Marshall',
        link: 'https://www.brandeismarshall.com/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/RodneySampson.jpeg'),
        name: 'Rodney Sampson',
        link: 'https://www.linkedin.com/in/rodneysampson/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
];

const blackinAIBoard = [
    {
        image: require('./../../assets/img/team/RedietAbebe.jpg'),
        name: 'Rediet Abebe',
        title: 'Director',
        link: 'https://www.cs.cornell.edu/~red/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/DevinGuillory.jpg'),
        name: 'Devin Guillory',
        title: 'Director',
        link: 'http://www.devinguillory.com/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/SanmiKoyejo.png'),
        name: 'Sanmi Koyejo',
        title: 'President',
        link: 'https://sanmi.cs.illinois.edu/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/EzinneNwanko.jpg'),
        name: 'Ezinne Nwankwo',
        title: 'Secretary',
        link: 'https://www.linkedin.com/in/ezinne-nwankwo-119586101/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/JoyceWilliams.jpg'),
        name: 'Joyce Williams',
        title: 'Treasure',
        link: '/',
        // icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
];

const communityTeam = [
    {
        image: require('./../../assets/img/team/IsaacDonis.jpg'),
        name: 'Isaac Donis',
        title: '',
        link: 'https://www.linkedin.com/in/isaacdonis/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/HassanKane.jpg'),
        name: 'Hassan Kane',
        title: '',
        link: 'https://www.linkedin.com/in/mohamed-hassan-kane-4b50328a/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/KrystalMaughan.jpg'),
        name: 'Krystal Maughan',
        title: '',
        link: 'https://www.linkedin.com/in/krystal-maughan-3563a215/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/mirian.png'),
        name: 'Mírian Silva',
        title: '',
        link: 'https://www.linkedin.com/in/mirianfsilva/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
];

function TeamMembers(props) {
    const { classes } = props;

    return (

            <Container className={classes.container}>
                <Grid container spacing={12}>
                    <Grid item xs={12}>
                        <Typography variant="h3" marked="center" align="center" className={classes.title}>
                            Our Team
                        </Typography>
                    </Grid>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Black in AI Board
                        </Typography>
                    </Grid>
                    {blackinAIBoard.map((tile) => (
                        <Grid item xs align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>

                                {tile.title}
                            </Typography>
                        </Grid>
                    ))}

                    <Divider variant="middle" className={classes.divider}/>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Advisory Board
                        </Typography>
                    </Grid>
                    {advisoryBoard.map((tile) => (
                        <Grid item xs align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>
                            </Typography>
                        </Grid>
                    ))}

                    <Divider variant="middle" className={classes.divider}/>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Community Team
                        </Typography>
                    </Grid>
                    <Grid container justify="center">
                        {communityTeam.map((tile) => (
                        <Grid key={tile} item xs md={2} align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>
                            </Typography>
                        </Grid>
                        ))}
                    </Grid>
                </Grid>
            </Container>

    );
}

TeamMembers.propTypes = {
    classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(TeamMembers);
<commit_msg>add Victor at community team<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import { Container, Divider, Avatar, Link} from '@material-ui/core/';
import Typography from '../Typography';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// images

const styles = (theme) => ({
    root: {
        display: 'flex',
        overflow: 'hidden',
        backgroundColor: theme.palette.primary.light,
    },
    container: {
        marginTop: theme.spacing(15),
        marginBottom: theme.spacing(10),
        display: 'flex',
        position: 'relative',
    },
    more: {
        marginTop: theme.spacing(5),
    },
    item: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'left',
        padding: theme.spacing(0, 5),
    },
    avatarSize: {
        width: theme.spacing(15),
        height: theme.spacing(15),
    },
    icon: {
        alignItems: 'left',
        padding: theme.spacing(1),
        color: theme.palette.primary.dark,
    },
    card: {
        height: '20px',
        marginTop: theme.spacing(1),
        marginBottom: theme.spacing(8),
    },
    divider: {
        backgroundColor: theme.palette.secondary.dark,
        margin: theme.spacing(2, 2),
        // flexItem: true,
        display: "flex",
        width:"150vh",
        height: "1px",
    },
    title: {
        marginBottom: theme.spacing(3),
        marginTop: theme.spacing(3),
        color: theme.palette.secondary,
    }
});

const advisoryBoard = [
    {
        image: require('./../../assets/img/team/MakindeAdeagbo.jpeg'),
        name: 'Makinde Adeagbo',
        link: 'https://www.linkedin.com/in/makinde/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/TimnitGebru.jpg'),
        name: 'Timnit Gebru',
        link: 'https://ai.stanford.edu/~tgebru/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/HabenGirma.jpg'),
        name: 'Haben Girma',
        link: 'https://www.linkedin.com/in/habengirma/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/AyannaHoward.jpg'),
        name: 'Ayanna Howard',
        link: 'https://www.linkedin.com/in/ayanna-howard/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/CharlesIsbell.jpg'),
        name: 'Charles Isbell',
        link: 'https://www.linkedin.com/in/charles-isbell-a12b808/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/BrandeisMarshall.jpg'),
        name: 'Brandeis Marshall',
        link: 'https://www.brandeismarshall.com/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/RodneySampson.jpeg'),
        name: 'Rodney Sampson',
        link: 'https://www.linkedin.com/in/rodneysampson/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
];

const blackinAIBoard = [
    {
        image: require('./../../assets/img/team/RedietAbebe.jpg'),
        name: 'Rediet Abebe',
        title: 'Director',
        link: 'https://www.cs.cornell.edu/~red/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/DevinGuillory.jpg'),
        name: 'Devin Guillory',
        title: 'Director',
        link: 'http://www.devinguillory.com/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/SanmiKoyejo.png'),
        name: 'Sanmi Koyejo',
        title: 'President',
        link: 'https://sanmi.cs.illinois.edu/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/EzinneNwanko.jpg'),
        name: 'Ezinne Nwankwo',
        title: 'Secretary',
        link: 'https://www.linkedin.com/in/ezinne-nwankwo-119586101/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/JoyceWilliams.jpg'),
        name: 'Joyce Williams',
        title: 'Treasure',
        link: '/',
        // icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
];

const communityTeam = [
    {
        image: require('./../../assets/img/team/IsaacDonis.jpg'),
        name: 'Isaac Donis',
        title: '',
        link: 'https://www.linkedin.com/in/isaacdonis/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/HassanKane.jpg'),
        name: 'Hassan Kane',
        title: '',
        link: 'https://www.linkedin.com/in/mohamed-hassan-kane-4b50328a/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/KrystalMaughan.jpg'),
        name: 'Krystal Maughan',
        title: '',
        link: 'https://www.linkedin.com/in/krystal-maughan-3563a215/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/mirian.png'),
        name: 'Mírian Silva',
        title: '',
        link: 'https://www.linkedin.com/in/mirianfsilva/',
        icon: <FontAwesomeIcon icon={["fab", "linkedin"]} size="1x"/>,
    },
    {
        image: require('./../../assets/img/team/victor.jpg'),
        name: 'Victor Silva',
        title: '',
        link: 'https://webdocs.cs.ualberta.ca/~vsilva/',
        icon: <FontAwesomeIcon icon={["fa", "link"]} size="1x"/>,
    },
];

function TeamMembers(props) {
    const { classes } = props;

    return (

            <Container className={classes.container}>
                <Grid container spacing={12}>
                    <Grid item xs={12}>
                        <Typography variant="h3" marked="center" align="center" className={classes.title}>
                            Our Team
                        </Typography>
                    </Grid>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Black in AI Board
                        </Typography>
                    </Grid>
                    {blackinAIBoard.map((tile) => (
                        <Grid item xs align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>

                                {tile.title}
                            </Typography>
                        </Grid>
                    ))}

                    <Divider variant="middle" className={classes.divider}/>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Advisory Board
                        </Typography>
                    </Grid>
                    {advisoryBoard.map((tile) => (
                        <Grid item xs align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>
                            </Typography>
                        </Grid>
                    ))}

                    <Divider variant="middle" className={classes.divider}/>

                    <Grid item xs={12}>
                        <Typography variant="h5" marked="center" align="center" className={classes.title}>
                            Community Team
                        </Typography>
                    </Grid>
                    <Grid container justify="center">
                        {communityTeam.map((tile) => (
                        <Grid key={tile} item xs md={2} align="center">
                            <Avatar alt={tile.name} src={tile.image} className={classes.avatarSize} />
                            <Typography className={classes.cardtitle} variant="h6" gutterBottom>
                                {tile.name} <Link target="_blank" rel="noopener" className={classes.icon} href={tile.link}>{tile.icon}</Link>
                            </Typography>
                        </Grid>
                        ))}
                    </Grid>
                </Grid>
            </Container>

    );
}

TeamMembers.propTypes = {
    classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(TeamMembers);
<|endoftext|>


<commit_before>const routeHelper = require('../../helpers/routes/route-helper')
const superTest = require('supertest')
const proxyquire = require('proxyquire').noPreserveCache()
const roles = require('../../..//app/constants/user-roles')
const hasRoleFunction = require('../../../app/authorisation').hasRole
const sinon = require('sinon')
require('sinon-bluebird')
const workloadTypes = require('../../../app/constants/workload-type')

const GET_REDUCTIONS_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/reductions'
const ADD_REDUCTION_PAGE_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/add-reduction'
const ADD_REDUCTION_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/add-reduction'
const EDIT_REDUCTION_PAGE_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/edit-reduction'
const EDIT_REDUCTION_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/edit-reduction'
const UPDATE_REDUCTION_STATUS_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/update-reduction-status'

const addReductionsRefData = {
  title: 'Title',
  subTitle: 'SubTitle',
  breadcrumbs: [],
  referenceData: [{ maxAllowanceHours: 0 }]
}

const existingReduction = {
  id: 1,
  reductionReasonId: 2,
  reductionHours: 12,
  effective_from: new Date(2000, 8, 1),
  effective_to: new Date(2000, 9, 1)
}

const successDataToPost = {
  id: undefined,
  reasonForReductionId: '1',
  reductionHours: '5',
  redStartYear: '2018',
  redStartMonth: '1',
  redStartDay: '1',
  redEndYear: '2018',
  redEndMonth: '7',
  redEndDay: '1',
  notes: 'This is a test note',
  status: 'SCHEDULED',
  reductionReason: { maxAllowanceHours: 0 }
}

const failureDataToPost = {
  reductionId: 1,
  reasonForReductionId: 1,
  reductionHours: '5',
  redStartYear: '',
  redStartMonth: '',
  redStartDay: '',
  redEndYear: '',
  redEndMonth: '',
  redEndDay: '',
  notes: 'This is a test note'
}

const returnedId = 1

var app
var route
var reductionsService
var getLastUpdated
var getSubNavStub
var authorisationService
var validRole = roles.MANAGER

var createMiddleWare = function () {
  return function (req, res, next) {
    req.user = {
      user_role: validRole
    }
    next()
  }
}

var initaliseApp = function (middleware) {
  authorisationService = {
    assertUserAuthenticated: sinon.stub(),
    hasRole: hasRoleFunction
  }
  getSubNavStub = sinon.stub()
  getLastUpdated = sinon.stub().resolves(new Date(2017, 11, 1))
  reductionsService = sinon.stub()
  reductionsService.getReductions = sinon.stub()
  reductionsService.getAddReductionsRefData = sinon.stub()
  reductionsService.addReduction = sinon.stub()
  reductionsService.updateReduction = sinon.stub()
  reductionsService.updateReductionStatus = sinon.stub()
  reductionsService.getReductionByReductionId = sinon.stub()
  route = proxyquire('../../../app/routes/reductions', {
    '../services/reductions-service': reductionsService,
    '../services/data/get-last-updated': getLastUpdated,
    '../authorisation': authorisationService,
    '../services/get-sub-nav': getSubNavStub
  })
  app = routeHelper.buildApp(route, middleware)
}

beforeEach(function () {
  initaliseApp(createMiddleWare())
})

describe('reductions route', function () {
  describe('For the get reductions route', function () {
    it('should respond with 200 when correct role is passed', function () {
      reductionsService.getReductions.resolves(addReductionsRefData)
      return superTest(app)
        .get(GET_REDUCTIONS_URL)
        .expect(200)
    })
  })

  describe('For the get reductions route', function () {
    it('should respond with 200 and the correct data', function () {
      reductionsService.getReductions.resolves(addReductionsRefData)
      return superTest(app)
        .get(GET_REDUCTIONS_URL)
        .expect(200)
    })
  })

  describe('For the add reductions page route', function () {
    it('should respond with 200 and the correct data and no existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .get(ADD_REDUCTION_PAGE_URL)
        .expect(200)
    })
  })

  describe('For the edit reductions page route', function () {
    it('should respond with 200 and the correct data and an existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.getReductionByReductionId.resolves(existingReduction)
      return superTest(app)
        .get(EDIT_REDUCTION_PAGE_URL + '?reductionId=' + existingReduction.id)
        .expect(200)
    })
  })

  describe('For the add reductions POST route', function () {
    it('should post the correct data and respond with 302', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.addReduction.resolves()
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post the correct data and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.addReduction.resolves(returnedId)
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })

  describe('For the edit reductions POST route', function () {
    it('should post the correct data and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReduction.resolves()
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })

  describe('For the update reduction status POST route', function () {
    it('should post the correct data with archived status and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(UPDATE_REDUCTION_STATUS_POST_URL)
        .send(Object.assign({}, successDataToPost, {status: 'ARCHIVED'}))
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1/reductions?archived=true')
    })

    it('should post the correct data with deleted status and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(UPDATE_REDUCTION_STATUS_POST_URL)
        .send(Object.assign({}, successDataToPost, {status: 'DELETED'}))
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1/reductions?deleted=true')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })
})
<commit_msg>Added id property to referenceData object<commit_after>const routeHelper = require('../../helpers/routes/route-helper')
const superTest = require('supertest')
const proxyquire = require('proxyquire').noPreserveCache()
const roles = require('../../..//app/constants/user-roles')
const hasRoleFunction = require('../../../app/authorisation').hasRole
const sinon = require('sinon')
require('sinon-bluebird')
const workloadTypes = require('../../../app/constants/workload-type')

const GET_REDUCTIONS_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/reductions'
const ADD_REDUCTION_PAGE_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/add-reduction'
const ADD_REDUCTION_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/add-reduction'
const EDIT_REDUCTION_PAGE_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/edit-reduction'
const EDIT_REDUCTION_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/edit-reduction'
const UPDATE_REDUCTION_STATUS_POST_URL = '/' + workloadTypes.PROBATION + '/offender-manager/1/update-reduction-status'

const addReductionsRefData = {
  title: 'Title',
  subTitle: 'SubTitle',
  breadcrumbs: [],
  referenceData: [{id:1, maxAllowanceHours: 0 }]
}

const existingReduction = {
  id: 1,
  reductionReasonId: 2,
  reductionHours: 12,
  effective_from: new Date(2000, 8, 1),
  effective_to: new Date(2000, 9, 1)
}

const successDataToPost = {
  id: undefined,
  reasonForReductionId: '1',
  reductionHours: '5',
  redStartYear: '2018',
  redStartMonth: '1',
  redStartDay: '1',
  redEndYear: '2018',
  redEndMonth: '7',
  redEndDay: '1',
  notes: 'This is a test note',
  status: 'SCHEDULED',
  reductionReason: { maxAllowanceHours: 0 }
}

const failureDataToPost = {
  reductionId: 1,
  reasonForReductionId: 1,
  reductionHours: '5',
  redStartYear: '',
  redStartMonth: '',
  redStartDay: '',
  redEndYear: '',
  redEndMonth: '',
  redEndDay: '',
  notes: 'This is a test note'
}

const returnedId = 1

var app
var route
var reductionsService
var getLastUpdated
var getSubNavStub
var authorisationService
var validRole = roles.MANAGER

var createMiddleWare = function () {
  return function (req, res, next) {
    req.user = {
      user_role: validRole
    }
    next()
  }
}

var initaliseApp = function (middleware) {
  authorisationService = {
    assertUserAuthenticated: sinon.stub(),
    hasRole: hasRoleFunction
  }
  getSubNavStub = sinon.stub()
  getLastUpdated = sinon.stub().resolves(new Date(2017, 11, 1))
  reductionsService = sinon.stub()
  reductionsService.getReductions = sinon.stub()
  reductionsService.getAddReductionsRefData = sinon.stub()
  reductionsService.addReduction = sinon.stub()
  reductionsService.updateReduction = sinon.stub()
  reductionsService.updateReductionStatus = sinon.stub()
  reductionsService.getReductionByReductionId = sinon.stub()
  route = proxyquire('../../../app/routes/reductions', {
    '../services/reductions-service': reductionsService,
    '../services/data/get-last-updated': getLastUpdated,
    '../authorisation': authorisationService,
    '../services/get-sub-nav': getSubNavStub
  })
  app = routeHelper.buildApp(route, middleware)
}

beforeEach(function () {
  initaliseApp(createMiddleWare())
})

describe('reductions route', function () {
  describe('For the get reductions route', function () {
    it('should respond with 200 when correct role is passed', function () {
      reductionsService.getReductions.resolves(addReductionsRefData)
      return superTest(app)
        .get(GET_REDUCTIONS_URL)
        .expect(200)
    })
  })

  describe('For the get reductions route', function () {
    it('should respond with 200 and the correct data', function () {
      reductionsService.getReductions.resolves(addReductionsRefData)
      return superTest(app)
        .get(GET_REDUCTIONS_URL)
        .expect(200)
    })
  })

  describe('For the add reductions page route', function () {
    it('should respond with 200 and the correct data and no existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .get(ADD_REDUCTION_PAGE_URL)
        .expect(200)
    })
  })

  describe('For the edit reductions page route', function () {
    it('should respond with 200 and the correct data and an existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.getReductionByReductionId.resolves(existingReduction)
      return superTest(app)
        .get(EDIT_REDUCTION_PAGE_URL + '?reductionId=' + existingReduction.id)
        .expect(200)
    })
  })

  describe('For the add reductions POST route', function () {
    it('should post the correct data and respond with 302', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.addReduction.resolves()
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post the correct data and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.addReduction.resolves(returnedId)
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .post(ADD_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })

  describe('For the edit reductions POST route', function () {
    it('should post the correct data and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReduction.resolves()
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(successDataToPost)
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })

  describe('For the update reduction status POST route', function () {
    it('should post the correct data with archived status and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(UPDATE_REDUCTION_STATUS_POST_URL)
        .send(Object.assign({}, successDataToPost, {status: 'ARCHIVED'}))
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1/reductions?archived=true')
    })

    it('should post the correct data with deleted status and respond with 200 for existing reduction', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(UPDATE_REDUCTION_STATUS_POST_URL)
        .send(Object.assign({}, successDataToPost, {status: 'DELETED'}))
        .expect(302, 'Found. Redirecting to /' + workloadTypes.PROBATION + '/offender-manager/1/reductions?deleted=true')
    })

    it('should post incorrect data and validation errors should be populated', function () {
      reductionsService.getAddReductionsRefData.resolves(addReductionsRefData)
      reductionsService.updateReductionStatus.resolves(returnedId)
      return superTest(app)
        .post(EDIT_REDUCTION_POST_URL)
        .send(failureDataToPost)
        .expect(400)
    })
  })
})
<|endoftext|>


<commit_before>"use babel";

const BufferedProcess = require('atom').BufferedProcess;
const path = require("path");
const fs = require("fs");

module.exports = {
  config: {
    elmMakeExecutablePath: {
      title: 'The elm-make executable path.',
      type: 'string',
      default: 'elm-make',
    }
  },
  activate() {
    if (!atom.packages.getLoadedPackage('language-elm')) {
      atom.notifications.addError("The Elm language package wasn't found.", {
        detail: 'Please install the `language-elm` package in your Settings view.'
      });
    }
    if (!atom.packages.getLoadedPackage('linter')) {
      atom.notifications.addError('The linter package not found.', {
        detail: 'Please install the `linter` package in your Settings view'
      });
    }
  },
  provideLinter() {
    const proc = process;
    return {
      grammarScopes: ['source.elm'],
      scope: 'file',
      lintOnFly: false,
      lint(textEditor) {
        return new Promise((resolve, reject) => {
          const filePath = textEditor.getPath();
          const lines = [];
          const executablePath = atom.config.get('linter-elm-make.elmMakeExecutablePath');
          const cwd = lookupElmPackage(path.dirname(textEditor.getPath()));
          if (cwd === null) {
            atom.notifications.addError("No elm-package.json beneath or above the edited file", {
            });
            return;
          }
          const options = {
            cwd: cwd,
            env: proc.env
          };
          const process = new BufferedProcess({
            command: executablePath,
            args: [filePath, '--warn', '--report=json', '--output=/dev/null'],
            options: options,
            stdout(data) {
              lines.push(data);
            },
            exit(code) {
              const text = lines[0];
              const json = (() => {
                try {
                  return JSON.parse(text.slice(0, text.indexOf("\n")));
                } catch (_error) {}
              })();
              if (atom.inDevMode()) {
                console.log(json);
              }
              if (!json) {
                return resolve([]);
              }
              const errors = json.map((error) => {
                return {
                  type: error.type,
                  html: `${error.overview}<br/><br/>${error.details.split('\n').join('<br/> ')}`,
                  filePath: error.file || filePath,
                  range: [
                    [error.region.start.line - 1, error.region.start.column - 1],
                    [error.region.end.line - 1, error.region.end.column - 1]
                  ]
                };
              });
              resolve(errors);
            }
          });
          process.onWillThrowError(({error, handle}) => {
            atom.notifications.addError("Failed to run " + executablePath, {
              detail: error.message
            });
            handle();
            return resolve([]);
          });
        });
      }
    };
  }
};

function lookupElmPackage(directory) {
  if (fs.existsSync(path.join(directory, "elm-package.json"))) {
    return directory;
  } else {
    const parentDirectory = path.join(directory, "..");
    if (fs.existsSync(parentDirectory)) {
      return lookupElmPackage(parentDirectory);
    } else {
      return null;
    }
  }
}
<commit_msg>Fix infinite recursion<commit_after>"use babel";

const BufferedProcess = require('atom').BufferedProcess;
const path = require("path");
const fs = require("fs");

module.exports = {
  config: {
    elmMakeExecutablePath: {
      title: 'The elm-make executable path.',
      type: 'string',
      default: 'elm-make',
    }
  },
  activate() {
    if (!atom.packages.getLoadedPackage('language-elm')) {
      atom.notifications.addError("The Elm language package wasn't found.", {
        detail: 'Please install the `language-elm` package in your Settings view.'
      });
    }
    if (!atom.packages.getLoadedPackage('linter')) {
      atom.notifications.addError('The linter package not found.', {
        detail: 'Please install the `linter` package in your Settings view'
      });
    }
  },
  provideLinter() {
    const proc = process;
    return {
      grammarScopes: ['source.elm'],
      scope: 'file',
      lintOnFly: false,
      lint(textEditor) {
        return new Promise((resolve, reject) => {
          const filePath = textEditor.getPath();
          const lines = [];
          const executablePath = atom.config.get('linter-elm-make.elmMakeExecutablePath');
          const cwd = lookupElmPackage(path.dirname(textEditor.getPath()));
          if (cwd === null) {
            atom.notifications.addError("No elm-package.json beneath or above the edited file", {
            });
            return;
          }
          const options = {
            cwd: cwd,
            env: proc.env
          };
          const process = new BufferedProcess({
            command: executablePath,
            args: [filePath, '--warn', '--report=json', '--output=/dev/null'],
            options: options,
            stdout(data) {
              lines.push(data);
            },
            exit(code) {
              const text = lines[0];
              const json = (() => {
                try {
                  return JSON.parse(text.slice(0, text.indexOf("\n")));
                } catch (_error) {}
              })();
              if (atom.inDevMode()) {
                console.log(json);
              }
              if (!json) {
                return resolve([]);
              }
              const errors = json.map((error) => {
                return {
                  type: error.type,
                  html: `${error.overview}<br/><br/>${error.details.split('\n').join('<br/> ')}`,
                  filePath: error.file || filePath,
                  range: [
                    [error.region.start.line - 1, error.region.start.column - 1],
                    [error.region.end.line - 1, error.region.end.column - 1]
                  ]
                };
              });
              resolve(errors);
            }
          });
          process.onWillThrowError(({error, handle}) => {
            atom.notifications.addError("Failed to run " + executablePath, {
              detail: error.message
            });
            handle();
            return resolve([]);
          });
        });
      }
    };
  }
};

function lookupElmPackage(directory) {
  if (fs.existsSync(path.join(directory, "elm-package.json"))) {
    return directory;
  } else {
    const parentDirectory = path.join(directory, "..");
    if (parentDirectory === directory) {
      return null;
    } else {
      return lookupElmPackage(parentDirectory);
    }
  }
}
<|endoftext|>


<commit_before>var plask = require('plask');
var glu = require('pex-glu');
var color = require('pex-color');

var Texture2D = glu.Texture2D;
var Color = color.Color;

function TextBox(str, fontFamily, fontSize, w, h, fontColor, bgColor, horizontalAlign, verticalAlign, offsetX, offsetY) {
	this.str = str;
    this.width = w;
    this.offsetX = offsetX || 0;
    this.offsetY = offsetY || 0;
    this.fontFamily = fontFamily;
    this.fontSize = fontSize;
    this.canvas = null;
    this.horizontalAlign = horizontalAlign || "center";
    this.verticalAlign = verticalAlign || "center";
    var paint = this.paint = new plask.SkPaint();
    paint.setFill();
    this.fontColor = fontColor || Color.White;
    this.bgColor = bgColor || Color.Black;
    paint.setColor(this.fontColor.r*255, this.fontColor.g*255, this.fontColor.b*255, this.fontColor.a*255);
    paint.setAntiAlias(true);
    paint.setFontFamilyPostScript(fontFamily);
    paint.setTextSize(fontSize);
    paint.setFilterBitmap(true);
    //paint.setDevKernText(true);
    // if (Config.UseLCDTextRendering) paint.setLCDRenderText(true);
    paint.setLCDRenderText(true);
    //paint.setSubpixelText(true);
    this.render(this.str);
}

TextBox.prototype.render = function(str, canvas) {
	var paint = this.paint;
	var metrics = paint.getFontMetrics();

	var textWidth = paint.measureText(str);
	while(textWidth > 8000) {
	  str = str.substr(0, str.length-1);
	  textWidth = paint.measureText(str);
	}

	var bounds = paint.measureTextBounds(str);

	var w = bounds[2] - bounds[0]; //right - left
	var h = bounds[3] - bounds[1]; //bottom - top

	if (!this.width) this.width = w;
	if (!this.height) this.height = h;

	if (w > this.width || h > this.height) {
	  console.log("TextBox render not enought space for '" + str + "' with font " + this.fontFamily);
	}

	if (!canvas) canvas = this.canvas;
	if (!canvas) canvas = this.canvas = new plask.SkCanvas(this.width, this.height);

	canvas.drawColor(this.bgColor.r*255, this.bgColor.g*255, this.bgColor.b*255, this.bgColor.a*255, plask.SkPaint.kClear_Mode);

	// align
	var px;
	if(this.horizontalAlign == "left") {
		px =  0;
	} else {
		// centered
		px =  0 - bounds[0] + (this.width-w)/2;
	}

	var py;
	if(this.verticalAlign == "top") {
		py = -bounds[1];
	} else if(this.verticalAlign == "bottom") {
		py = this.height;
	} else {
		// centered
		py =  h - bounds[3] + (this.height-h)/2;
	}

	px += this.offsetX;
	py += this.offsetY;

	canvas.drawText(paint, str, px, py);
}

// TODO: pass some options for texture creating
TextBox.prototype.getTexture = function(anisotropy) {
	var gl = glu.Context.currentContext;

	var aniso = anisotropy || 0;

	this.texture = this.texture || new Texture2D.create(this.width, this.height, { repeat: true });
	this.texture.bind();
	gl.texImage2DSkCanvas(gl.TEXTURE_2D, 0, this.canvas);
	this.texture.anisotropy(aniso);
	this.texture.generateMipmap();
	gl.bindTexture(gl.TEXTURE_2D, null);

	return this.texture;
};

module.exports = TextBox;
<commit_msg>TextBox: control over vertical align and fix for height parameter<commit_after>var plask = require('plask');
var glu = require('pex-glu');
var color = require('pex-color');

var Texture2D = glu.Texture2D;
var Color = color.Color;

function TextBox(str, fontFamily, fontSize, w, h, fontColor, bgColor, horizontalAlign, verticalAlign, offsetX, offsetY) {
	this.str = str;
    this.width = w;
    this.height = h;
    this.offsetX = offsetX || 0;
    this.offsetY = offsetY || 0;
    this.fontFamily = fontFamily;
    this.fontSize = fontSize;
    this.canvas = null;
    this.horizontalAlign = horizontalAlign || "center";
    this.verticalAlign = verticalAlign || "center";
    var paint = this.paint = new plask.SkPaint();
    paint.setFill();
    this.fontColor = fontColor || Color.White;
    this.bgColor = bgColor || Color.Black;
    paint.setColor(this.fontColor.r*255, this.fontColor.g*255, this.fontColor.b*255, this.fontColor.a*255);
    paint.setAntiAlias(true);
    paint.setFontFamilyPostScript(fontFamily);
    paint.setTextSize(fontSize);
    paint.setFilterBitmap(true);
    //paint.setDevKernText(true);
    // if (Config.UseLCDTextRendering) paint.setLCDRenderText(true);
    paint.setLCDRenderText(true);
    //paint.setSubpixelText(true);
    this.render(this.str);
}

TextBox.prototype.render = function(str, canvas) {
	var paint = this.paint;
	var metrics = paint.getFontMetrics();

	var textWidth = paint.measureText(str);
	while(textWidth > 8000) {
	  str = str.substr(0, str.length-1);
	  textWidth = paint.measureText(str);
	}

	var bounds = paint.measureTextBounds(str);

	var w = bounds[2] - bounds[0]; //right - left
	var h = bounds[3] - bounds[1]; //bottom - top

	if (!this.width) this.width = w;
	if (!this.height) this.height = h;

	if (w > this.width || h > this.height) {
	  console.log("TextBox render not enought space for '" + str + "' with font " + this.fontFamily);
	}

	if (!canvas) canvas = this.canvas;
	if (!canvas) canvas = this.canvas = new plask.SkCanvas(this.width, this.height);

	canvas.drawColor(this.bgColor.r*255, this.bgColor.g*255, this.bgColor.b*255, this.bgColor.a*255, plask.SkPaint.kClear_Mode);

	// align
	var px;
	if(this.horizontalAlign == "left") {
		px =  0;
	} else {
		// centered
		px =  0 - bounds[0] + (this.width-w)/2;
	}

	var py;
	if(this.verticalAlign == "top") {
		py = -bounds[1];
	} else if(this.verticalAlign == "bottom") {
		py = this.height;
	} else if(this.verticalAlign == "fixed") {
		py = 0.0;
	} else {
		// centered
		py =  h - bounds[3] + (this.height-h)/2;
	}

	px += this.offsetX;
	py += this.offsetY;

	canvas.drawText(paint, str, px, py);
}

// TODO: pass some options for texture creating
TextBox.prototype.getTexture = function(anisotropy) {
	var gl = glu.Context.currentContext;

	var aniso = anisotropy || 0;

	this.texture = this.texture || new Texture2D.create(this.width, this.height, { repeat: true });
	this.texture.bind();
	gl.texImage2DSkCanvas(gl.TEXTURE_2D, 0, this.canvas);
	this.texture.anisotropy(aniso);
	this.texture.generateMipmap();
	gl.bindTexture(gl.TEXTURE_2D, null);

	return this.texture;
};

module.exports = TextBox;
<|endoftext|>


<commit_before>/* global describe, before, it */

const siteSettings = require('../../../js/state/siteSettings')
const assert = require('assert')
const Immutable = require('immutable')
let siteSettingsMap = new Immutable.Map()

describe('siteSettings', function () {
  describe('simple URL host pattern', function () {
    before(function () {
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)
    })
    it('can obtain a site setting from a host pattern', function * () {
      const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
    })
    it('can obtain a site setting from an exact URL', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
    })
    it('can obtain a site setting from a URL at that host', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects#test')
      assert.strictEqual(setting.get('prop1'), 1)
    })
  })
  describe('any port pattern', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2)
    })
    it('can obtain with a wildcard port for host pattern', function * () {
      const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('can obtain without a port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('can obtain with a specific port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com:8080/projects#test')
      assert.strictEqual(setting.get('prop1'), 2)
    })
  })
  describe('all https protocol pattern', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*', 'prop1', 3)
    })
    it('Can obtain from https url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 3)
    })
    it('Cannot obtain from http url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com')
      assert.strictEqual(setting, undefined)
    })
  })
  describe('http or https specific URLs', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https?://www.brave.com', 'prop1', 4)
    })
    it('Can obtain from https url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 4)
    })
    it('Can obtain from http url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 4)
    })
  })
  describe('subdomain wildcards', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https?://*.brave.com:*', 'prop1', 5)
    })
    it('Can obtain from no subdomain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from a single subdomain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from multiple subdomains', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from multiple subdomains and a different port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com:99/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Cannot obtain from a diff domain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brianbondy.com/projects')
      assert.strictEqual(setting, undefined)
    })
  })
  describe('Overwriting a setting works', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 2)
    })
    it('can overwrites what is needed but does not ', function * () {
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('merging settings does not lose other data', function * () {
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop2', 3)
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
      assert.strictEqual(setting.get('prop2'), 3)
    })
  })
  describe('More specific rules override', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop1', 3)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop2', 3)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop3', 3)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop2', 2)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop1', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop2', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop3', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop4', 4)
    })
    it('can obtain a site setting from a host pattern', function * () {
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
      assert.strictEqual(setting.get('prop2'), undefined)
      assert.strictEqual(setting.get('prop3'), undefined)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*')
      assert.strictEqual(setting.get('prop1'), 2)
      assert.strictEqual(setting.get('prop2'), 2)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://*.brave.com')
      assert.strictEqual(setting.get('prop1'), 3)
      assert.strictEqual(setting.get('prop2'), 3)
      assert.strictEqual(setting.get('prop3'), 3)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, '*')
      assert.strictEqual(setting.get('prop1'), 4)
      assert.strictEqual(setting.get('prop2'), 4)
      assert.strictEqual(setting.get('prop3'), 4)
      assert.strictEqual(setting.get('prop4'), 4)
    })
    it('can obtain properly combined settings', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
      assert.strictEqual(setting.get('prop2'), 2)
      assert.strictEqual(setting.get('prop3'), 3)
      assert.strictEqual(setting.get('prop4'), 4)
    })
  })
})
<commit_msg>Add siteSettings unit test for behavior with trailing slash<commit_after>/* global describe, before, it */

const siteSettings = require('../../../js/state/siteSettings')
const assert = require('assert')
const Immutable = require('immutable')
let siteSettingsMap = new Immutable.Map()

describe('siteSettings', function () {
  describe('simple URL host pattern', function () {
    before(function () {
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)
    })
    it('can obtain a site setting from a host pattern', function * () {
      const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
    })
    it('can obtain a site setting from an exact URL', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
    })
    it('can obtain a site setting from a URL at that host', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects#test')
      assert.strictEqual(setting.get('prop1'), 1)
    })
  })
  describe('any port pattern', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2)
    })
    it('can obtain with a wildcard port for host pattern', function * () {
      const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('can obtain without a port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('can obtain with a specific port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com:8080/projects#test')
      assert.strictEqual(setting.get('prop1'), 2)
    })
  })
  describe('all https protocol pattern', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*', 'prop1', 3)
    })
    it('Can obtain from https url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 3)
    })
    it('Cannot obtain from http url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com')
      assert.strictEqual(setting, undefined)
    })
  })
  describe('http or https specific URLs', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https?://www.brave.com', 'prop1', 4)
    })
    it('Can obtain from https url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 4)
    })
    it('Can obtain from http url', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 4)
    })
  })
  describe('exact URL pattern', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com/', 'prop1', 1)
    })
    it('Can obtain for the exact URL', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/')
      assert.strictEqual(setting.get('prop1'), 1)
    })
    it('Does not obtain for non-equal URL', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), undefined)
    })
    it('Does not obtain for origin string', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), undefined)
    })
  })
  describe('subdomain wildcards', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https?://*.brave.com:*', 'prop1', 5)
    })
    it('Can obtain from no subdomain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from a single subdomain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from multiple subdomains', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Can obtain from multiple subdomains and a different port', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com:99/projects')
      assert.strictEqual(setting.get('prop1'), 5)
    })
    it('Cannot obtain from a diff domain', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brianbondy.com/projects')
      assert.strictEqual(setting, undefined)
    })
  })
  describe('Overwriting a setting works', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 2)
    })
    it('can overwrites what is needed but does not ', function * () {
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
    })
    it('merging settings does not lose other data', function * () {
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop2', 3)
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 2)
      assert.strictEqual(setting.get('prop2'), 3)
    })
  })
  describe('More specific rules override', function () {
    before(function () {
      siteSettingsMap = new Immutable.Map()

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop1', 3)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop2', 3)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop3', 3)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop2', 2)

      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop1', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop2', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop3', 4)
      siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, '*', 'prop4', 4)
    })
    it('can obtain a site setting from a host pattern', function * () {
      let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
      assert.strictEqual(setting.get('prop2'), undefined)
      assert.strictEqual(setting.get('prop3'), undefined)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*')
      assert.strictEqual(setting.get('prop1'), 2)
      assert.strictEqual(setting.get('prop2'), 2)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://*.brave.com')
      assert.strictEqual(setting.get('prop1'), 3)
      assert.strictEqual(setting.get('prop2'), 3)
      assert.strictEqual(setting.get('prop3'), 3)

      setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, '*')
      assert.strictEqual(setting.get('prop1'), 4)
      assert.strictEqual(setting.get('prop2'), 4)
      assert.strictEqual(setting.get('prop3'), 4)
      assert.strictEqual(setting.get('prop4'), 4)
    })
    it('can obtain properly combined settings', function * () {
      const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com')
      assert.strictEqual(setting.get('prop1'), 1)
      assert.strictEqual(setting.get('prop2'), 2)
      assert.strictEqual(setting.get('prop3'), 3)
      assert.strictEqual(setting.get('prop4'), 4)
    })
  })
})
<|endoftext|>


<commit_before>var async = require('async');
var listToArray = require('list-to-array');

var evalDependsOn = require('../../fields/utils/evalDependsOn.js');

var MONGO_INDEX_CONSTRAINT_ERROR_REGEXP = /E11000 duplicate key error index\: [^\$]+\$(\w+) dup key\: \{ \: "([^"]+)" \}/;

// Adds a validation message to the errors object in the common format
function addValidationError (options, errors, field, type, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	var error = '';
	if (typeof detail === 'string') {
		error = detail;
	} else {
		if (type === 'required' && options.requiredMessages && options.requiredMessages[field.path]) {
			error = options.requiredMessages[field.path];
		} else if (type === 'invalid' && options.invalidMessages && options.invalidMessages[field.path]) {
			error = options.invalidMessages[field.path];
		} else {
			error = field.path.substr(0, 1).toUpperCase() + field.path.substr(1) + ' is ' + type;
		}
	}
	errors[field.path] = {
		type: type,
		error: error,
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
};

// Adds a field update error message to the errors object in the common format
function addFieldUpdateError (errors, field, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	errors[field.path] = {
		error: typeof detail === 'string' ? detail : field.path + ' error',
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
}

function updateItem (item, data, options, callback) {
	/* Process arguments and options */
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}

	// update fields with noedit: true set if fields have been explicitly
	// provided, or if the ignoreNoEdit option is true
	var ignoreNoEdit = !!(options.fields || options.ignoreNoEdit);

	// fields defaults to all the fields in the list
	var fields = options.fields || this.fieldsArray;
	// fields can be a list or array of field paths or Field instances
	fields = listToArray(fields).map(function (field) {
		// TODO: Check that field is an instance of Field
		return (typeof field === 'string') ? this.fields[field] : field;
	}, this);
	// check for invalid fields
	if (fields.indexOf(undefined) >= 0) {
		return callback({
			error: 'invalid configuration',
			detail: 'Invalid path specified in fields to update [' + options.fields + '] for list ' + this.key,
		});
	}

	// you can optionally require fields that aren't required in the schema
	// note that if fields are required in the schema, they will always be checked
	//
	// this option supports the backwards compatible { path: true } format, or a
	// list or array of field paths to validate
	var requiredFields = options.required;
	var requiredFieldPaths = {};
	if (typeof requiredFields === 'string') {
		requiredFields = listToArray(requiredFields);
	}
	if (Array.isArray(requiredFields)) {
		requiredFields.forEach(function (path) {
			requiredFieldPaths[path] = true;
		});
	} else if (typeof requiredFields === 'object') {
		requiredFieldPaths = requiredFields;
	}

	/* Field Validation */
	// TODO: If a field is required but not specified in the provided fields array
	// we should explicitly include it in the set of fields to validate
	var validationErrors = {};
	function doFieldValidation (field, done) {
		// only validate fields we're going to update
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// Note; we don't pass back validation errors to the callback, because we don't
		// want to break the async loop before all the fields have been validated.
		field.validateInput(data, function (valid, detail) {
			if (!valid) {
				addValidationError(options, validationErrors, field, 'invalid', detail);
				done();
			} else {
				if ((field.required || requiredFieldPaths[field.path])
					&& (!field.dependsOn || evalDependsOn(field.dependsOn, data)))
				{
					field.validateRequiredInput(item, data, function (valid, detail) {
						if (!valid) {
							addValidationError(options, validationErrors, field, 'required', detail);
						}
						done();
					});
				} else {
					done();
				}
			}
		});
	}

	/* Field Updates */
	var updateErrors = {};
	function doFieldUpdate (field, done) {
		var callback = function (err) {
			// Note; we don't pass back errors to the callback, because we don't want
			// to break the async loop before all the fields have been updated.
			if (err) {
				addFieldUpdateError(updateErrors, field, err);
			}
			done();
		};
		// check we should update the field
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// all fields have (item, data) as the first two arguments
		var updateArgs = [item, data];
		// some fields support an optional third argument: files
		if (field.updateItem.length > 3) {
			updateArgs.push(options.files);
		}
		// callback is always the last argument
		updateArgs.push(callback);
		// call field.updateItem with the arguments
		field.updateItem.apply(field, updateArgs);
	}

	/* Track plugin support */
	// If the track plugin is enabled for the list, it looks for ._req_user to
	// detect the user that performed the updated. Default it to the user
	// specified in the options.
	if (options.user) {
		item._req_user = options.user;
	}

	/* Flow control */
	async.series([
		/* Process validation */
		function (passedValidation) {
			async.each(fields, doFieldValidation, function () {
				if (Object.keys(validationErrors).length) {
					return passedValidation({
						error: 'validation errors',
						detail: validationErrors,
					});
				}
				passedValidation();
			});
		},
		/* Apply updates to fields */
		function (doneUpdate) {
			async.each(fields, doFieldUpdate, function () {
				if (Object.keys(updateErrors).length) {
					return doneUpdate({
						error: 'field errors',
						detail: updateErrors,
					});
				}
				item.save(doneUpdate);
			});
		},
	],

	/* Done */
	function (err) {
		if (err) {
			// Try to make mongoose index constraint errors more friendly
			// This is brittle, but should return a more human-readable error message
			if (err.code === 11000) {
				var indexConstraintError = MONGO_INDEX_CONSTRAINT_ERROR_REGEXP.exec(err.errmsg);
				if (indexConstraintError) {
					var probableFieldPath = indexConstraintError[1];
					probableFieldPath = probableFieldPath.substr(0, probableFieldPath.lastIndexOf('_'));
					return callback({
						error: 'database error',
						detail: 'Duplicate ' + probableFieldPath + ' value "' + indexConstraintError[2] + '" already exists',
					});
				}
			}
			return callback({
				error: 'database error',
				detail: err,
			});
		}
		return callback();
	});
}

module.exports = updateItem;
<commit_msg>Fixing an issue where errors are incorrectly wrapped in API responses<commit_after>var async = require('async');
var listToArray = require('list-to-array');

var evalDependsOn = require('../../fields/utils/evalDependsOn.js');

var MONGO_INDEX_CONSTRAINT_ERROR_REGEXP = /E11000 duplicate key error index\: [^\$]+\$(\w+) dup key\: \{ \: "([^"]+)" \}/;

// Adds a validation message to the errors object in the common format
function addValidationError (options, errors, field, type, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	var error = '';
	if (typeof detail === 'string') {
		error = detail;
	} else {
		if (type === 'required' && options.requiredMessages && options.requiredMessages[field.path]) {
			error = options.requiredMessages[field.path];
		} else if (type === 'invalid' && options.invalidMessages && options.invalidMessages[field.path]) {
			error = options.invalidMessages[field.path];
		} else {
			error = field.path.substr(0, 1).toUpperCase() + field.path.substr(1) + ' is ' + type;
		}
	}
	errors[field.path] = {
		type: type,
		error: error,
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
};

// Adds a field update error message to the errors object in the common format
function addFieldUpdateError (errors, field, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	errors[field.path] = {
		error: typeof detail === 'string' ? detail : field.path + ' error',
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
}

function updateItem (item, data, options, callback) {
	/* Process arguments and options */
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}

	// update fields with noedit: true set if fields have been explicitly
	// provided, or if the ignoreNoEdit option is true
	var ignoreNoEdit = !!(options.fields || options.ignoreNoEdit);

	// fields defaults to all the fields in the list
	var fields = options.fields || this.fieldsArray;
	// fields can be a list or array of field paths or Field instances
	fields = listToArray(fields).map(function (field) {
		// TODO: Check that field is an instance of Field
		return (typeof field === 'string') ? this.fields[field] : field;
	}, this);
	// check for invalid fields
	if (fields.indexOf(undefined) >= 0) {
		return callback({
			error: 'invalid configuration',
			detail: 'Invalid path specified in fields to update [' + options.fields + '] for list ' + this.key,
		});
	}

	// you can optionally require fields that aren't required in the schema
	// note that if fields are required in the schema, they will always be checked
	//
	// this option supports the backwards compatible { path: true } format, or a
	// list or array of field paths to validate
	var requiredFields = options.required;
	var requiredFieldPaths = {};
	if (typeof requiredFields === 'string') {
		requiredFields = listToArray(requiredFields);
	}
	if (Array.isArray(requiredFields)) {
		requiredFields.forEach(function (path) {
			requiredFieldPaths[path] = true;
		});
	} else if (typeof requiredFields === 'object') {
		requiredFieldPaths = requiredFields;
	}

	/* Field Validation */
	// TODO: If a field is required but not specified in the provided fields array
	// we should explicitly include it in the set of fields to validate
	var validationErrors = {};
	function doFieldValidation (field, done) {
		// only validate fields we're going to update
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// Note; we don't pass back validation errors to the callback, because we don't
		// want to break the async loop before all the fields have been validated.
		field.validateInput(data, function (valid, detail) {
			if (!valid) {
				addValidationError(options, validationErrors, field, 'invalid', detail);
				done();
			} else {
				if ((field.required || requiredFieldPaths[field.path])
					&& (!field.dependsOn || evalDependsOn(field.dependsOn, data)))
				{
					field.validateRequiredInput(item, data, function (valid, detail) {
						if (!valid) {
							addValidationError(options, validationErrors, field, 'required', detail);
						}
						done();
					});
				} else {
					done();
				}
			}
		});
	}

	/* Field Updates */
	var updateErrors = {};
	function doFieldUpdate (field, done) {
		var callback = function (err) {
			// Note; we don't pass back errors to the callback, because we don't want
			// to break the async loop before all the fields have been updated.
			if (err) {
				addFieldUpdateError(updateErrors, field, err);
			}
			done();
		};
		// check we should update the field
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// all fields have (item, data) as the first two arguments
		var updateArgs = [item, data];
		// some fields support an optional third argument: files
		if (field.updateItem.length > 3) {
			updateArgs.push(options.files);
		}
		// callback is always the last argument
		updateArgs.push(callback);
		// call field.updateItem with the arguments
		field.updateItem.apply(field, updateArgs);
	}

	/* Track plugin support */
	// If the track plugin is enabled for the list, it looks for ._req_user to
	// detect the user that performed the updated. Default it to the user
	// specified in the options.
	if (options.user) {
		item._req_user = options.user;
	}

	/* Flow control */
	async.series([
		/* Process validation */
		function (passedValidation) {
			async.each(fields, doFieldValidation, function () {
				if (Object.keys(validationErrors).length) {
					return passedValidation({
						error: 'validation errors',
						detail: validationErrors,
					});
				}
				passedValidation();
			});
		},
		/* Apply updates to fields */
		function (doneUpdate) {
			async.each(fields, doFieldUpdate, function () {
				if (Object.keys(updateErrors).length) {
					return doneUpdate({
						error: 'field errors',
						detail: updateErrors,
					});
				}
				item.save(doneUpdate);
			});
		},
	],

	/* Done */
	function (err) {
		if (err) {
			if (err instanceof Error) {
				// Try to make mongoose index constraint errors more friendly
				// This is brittle, but should return a more human-readable error message
				if (err.code === 11000) {
					var indexConstraintError = MONGO_INDEX_CONSTRAINT_ERROR_REGEXP.exec(err.errmsg);
					if (indexConstraintError) {
						var probableFieldPath = indexConstraintError[1];
						probableFieldPath = probableFieldPath.substr(0, probableFieldPath.lastIndexOf('_'));
						return callback({
							error: 'database error',
							detail: 'Duplicate ' + probableFieldPath + ' value "' + indexConstraintError[2] + '" already exists',
						});
					}
				}
				// Wrap Error objects in the standard format, they're most likely
				// a database error (not sure if we can make this more specific?)
				return callback({
					error: 'database error',
					detail: err,
				});
			} else {
				// Return other error object directly
				return callback(err);
			}
		}
		return callback();
	});
}

module.exports = updateItem;
<|endoftext|>


<commit_before>import React from 'react';
import {shallow} from 'enzyme';

import {multiFilePatchBuilder} from '../builder/patch';
import {pullRequestBuilder} from '../builder/pr';
import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-review-comments-view';

describe('PullRequestCommentsView', function() {
  it('adjusts the position for comments after hunk headers', function() {
    const {multiFilePatch} = multiFilePatchBuilder()
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file0.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)'));
        fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)'));
        fp.addHunk(h => {
          h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)');
        });
      })
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file1.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('15 (1)').added('16 (2)').unchanged('17 (3)'));
        fp.addHunk(h => h.oldRow(20).unchanged('18 (5)').deleted('19 (6)', '20 (7)', '21 (8)').unchanged('22 (9)'));
      })
      .build();

    const pr = pullRequestBuilder()
      .addReview(r => {
        r.addComment(c => c.id(0).path('file0.txt').position(2).body('one'));
        r.addComment(c => c.id(1).path('file0.txt').position(15).body('three'));
        r.addComment(c => c.id(1).path('file1.txt').position(7).body('three'));
      })
      .build();

    const wrapper = shallow(<PullRequestCommentsView multiFilePatch={multiFilePatch} reviews={pr.reviews} commentThreads={pr.commentThreads} />);

    assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]);
    assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]);
    assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]);
  });

  it('does not render comment if position is null', function() {
    const {multiFilePatch} = multiFilePatchBuilder()
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file0.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)'));
        fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)'));
        fp.addHunk(h => {
          h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)');
        });
      })
      .build();

    const pr = pullRequestBuilder()
      .addReview(r => {
        r.addComment(c => c.id(0).path('file0.txt').position(2).body('one'));
        r.addComment(c => c.id(1).path('file0.txt').position(null).body('three'));
      })
      .build();

    const wrapper = shallow(<PullRequestCommentsView multiFilePatch={multiFilePatch} reviews={pr.reviews} commentThreads={pr.commentThreads} />);

    const comments = wrapper.find('PullRequestCommentView');
    assert.lengthOf(comments, 1);
    assert.strictEqual(comments.at(0).prop('comment').body, 'one');
  });
});

describe('PullRequestCommentView', function() {
  const avatarUrl = 'https://avatars3.githubusercontent.com/u/3781742?s=40&v=4';
  const login = 'annthurium';
  const commentUrl = 'https://github.com/kuychaco/test-repo/pull/4#discussion_r244214873';
  const createdAt = '2018-12-27T17:51:17Z';
  const bodyHTML = '<div> yo yo </div>';
  const switchToIssueish = () => {};

  function buildApp(overrideProps = {}, opts = {}) {
    const props = {
      comment: {
        bodyHTML,
        url: commentUrl,
        createdAt,
        author: {
          avatarUrl,
          login,
        },
        ...overrideProps,
      },
      switchToIssueish,
      ...opts,
    };

    return (
      <PullRequestCommentView {...props} />
    );
  }
  it('renders the PullRequestCommentReview information', function() {
    const wrapper = shallow(buildApp());
    const avatar = wrapper.find('.github-PrComment-avatar');

    assert.strictEqual(avatar.getElement('src').props.src, avatarUrl);
    assert.strictEqual(avatar.getElement('alt').props.alt, login);

    assert.isTrue(wrapper.text().includes(`${login} commented`));

    const a = wrapper.find('.github-PrComment-timeAgo');
    assert.strictEqual(a.getElement('href').props.href, commentUrl);

    const timeAgo = wrapper.find('Timeago');
    assert.strictEqual(timeAgo.prop('time'), createdAt);

    const githubDotcomMarkdown = wrapper.find('GithubDotcomMarkdown');
    assert.strictEqual(githubDotcomMarkdown.prop('html'), bodyHTML);
    assert.strictEqual(githubDotcomMarkdown.prop('switchToIssueish'), switchToIssueish);
  });

  it('contains the text `someone commented` for null authors', function() {
    const wrapper = shallow(buildApp({author: null}));
    assert.isTrue(wrapper.text().includes('someone commented'));
  });
});
<commit_msg>give `PullRequestCommentsView` tests the new getBufferRowForDiffPosition prop<commit_after>import React from 'react';
import {shallow} from 'enzyme';

import {multiFilePatchBuilder} from '../builder/patch';
import {pullRequestBuilder} from '../builder/pr';
import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-review-comments-view';

describe('PullRequestCommentsView', function() {
  it('adjusts the position for comments after hunk headers', function() {
    const {multiFilePatch} = multiFilePatchBuilder()
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file0.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)'));
        fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)'));
        fp.addHunk(h => {
          h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)');
        });
      })
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file1.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('15 (1)').added('16 (2)').unchanged('17 (3)'));
        fp.addHunk(h => h.oldRow(20).unchanged('18 (5)').deleted('19 (6)', '20 (7)', '21 (8)').unchanged('22 (9)'));
      })
      .build();

    const pr = pullRequestBuilder()
      .addReview(r => {
        r.addComment(c => c.id(0).path('file0.txt').position(2).body('one'));
        r.addComment(c => c.id(1).path('file0.txt').position(15).body('three'));
        r.addComment(c => c.id(1).path('file1.txt').position(7).body('three'));
      })
      .build();

    const wrapper = shallow(<PullRequestCommentsView getBufferRowForDiffPosition={multiFilePatch.getBufferRowForDiffPosition} reviews={pr.reviews} commentThreads={pr.commentThreads} />);

    assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]);
    assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]);
    assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]);
  });

  it('does not render comment if position is null', function() {
    const {multiFilePatch} = multiFilePatchBuilder()
      .addFilePatch(fp => {
        fp.setOldFile(f => f.path('file0.txt'));
        fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)'));
        fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)'));
        fp.addHunk(h => {
          h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)');
        });
      })
      .build();

    const pr = pullRequestBuilder()
      .addReview(r => {
        r.addComment(c => c.id(0).path('file0.txt').position(2).body('one'));
        r.addComment(c => c.id(1).path('file0.txt').position(null).body('three'));
      })
      .build();

    const wrapper = shallow(<PullRequestCommentsView multiFilePatch={multiFilePatch} reviews={pr.reviews} commentThreads={pr.commentThreads} getBufferRowForDiffPosition={multiFilePatch.getBufferRowForDiffPosition} />);

    const comments = wrapper.find('PullRequestCommentView');
    assert.lengthOf(comments, 1);
    assert.strictEqual(comments.at(0).prop('comment').body, 'one');
  });
});

describe('PullRequestCommentView', function() {
  const avatarUrl = 'https://avatars3.githubusercontent.com/u/3781742?s=40&v=4';
  const login = 'annthurium';
  const commentUrl = 'https://github.com/kuychaco/test-repo/pull/4#discussion_r244214873';
  const createdAt = '2018-12-27T17:51:17Z';
  const bodyHTML = '<div> yo yo </div>';
  const switchToIssueish = () => {};

  function buildApp(overrideProps = {}, opts = {}) {
    const props = {
      comment: {
        bodyHTML,
        url: commentUrl,
        createdAt,
        author: {
          avatarUrl,
          login,
        },
        ...overrideProps,
      },
      switchToIssueish,
      ...opts,
    };

    return (
      <PullRequestCommentView {...props} />
    );
  }
  it('renders the PullRequestCommentReview information', function() {
    const wrapper = shallow(buildApp());
    const avatar = wrapper.find('.github-PrComment-avatar');

    assert.strictEqual(avatar.getElement('src').props.src, avatarUrl);
    assert.strictEqual(avatar.getElement('alt').props.alt, login);

    assert.isTrue(wrapper.text().includes(`${login} commented`));

    const a = wrapper.find('.github-PrComment-timeAgo');
    assert.strictEqual(a.getElement('href').props.href, commentUrl);

    const timeAgo = wrapper.find('Timeago');
    assert.strictEqual(timeAgo.prop('time'), createdAt);

    const githubDotcomMarkdown = wrapper.find('GithubDotcomMarkdown');
    assert.strictEqual(githubDotcomMarkdown.prop('html'), bodyHTML);
    assert.strictEqual(githubDotcomMarkdown.prop('switchToIssueish'), switchToIssueish);
  });

  it('contains the text `someone commented` for null authors', function() {
    const wrapper = shallow(buildApp({author: null}));
    assert.isTrue(wrapper.text().includes('someone commented'));
  });
});
<|endoftext|>