<commit_before>if (!this.navigator) {
  var lib = require('../utils');
}

var items = [
  {
    name: 'Utkin',
    age: 35
  },
  {
    name: 'Ivanov'
  },
  {
    name: 'Petrov',
    age: 38
  },
  {
    age: 25,
    salary: 87000
  }
];

var plain = ['Utkin', 'Ivanov', 'Petrov'];
var newProperty = {
  city: 'Khabarovsk',
  zip: '680009'
};

describe("Check Utils.js", function () {
  beforeEach(function () {
    expect(items.length).toEqual(4);
    expect(lib.filterHaving(items, 'salary').length).toEqual(1);
  });

  describe("filterHaving checks", function () {
    it("filterHaving() length = 0", function () {
      expect(lib.filterHaving(items).length).toEqual(0);
    });
    it("filterHaving(xxx) length = 0", function () {
      expect(lib.filterHaving(items, 'xxx').length).toEqual(0);
    });
    it("filterHaving(age) length = 2", function () {
      expect(lib.filterHaving(items, 'age').length).toEqual(3);
    });
  });

  describe("extract checks", function () {
    it("extract(salary) length = 1", function () {
      expect(lib.filterHaving(items, 'salary').length).toEqual(1);
    });
    it("extract(salary)[0] has salary defined", function () {
      expect(lib.filterHaving(items, 'salary')[0].salary).toBeDefined();
    });
    it("extract(salary)[0] has name undefined", function () {
      expect(lib.filterHaving(items, 'salary')[0].name).toBeUndefined();
    });
    it("extract(salary)[0].salary == 87000", function () {
      expect(lib.filterHaving(items, 'salary')[0].salary).toEqual(87000);
    });
  });

  describe("plain checks", function () {
    it("plain(name) length = 4", function () {
      expect(lib.plain(items, 'name').length).toEqual(4);
    });
    it("plain(name) to contain undefined", function () {
      expect(lib.plain(items, 'name')).toContain(undefined);
    });
    it("plain(name) not to contain null", function () {
      expect(lib.plain(items, 'name')).not.toContain(null);
    });
    it("plain(name) length = 3", function () {
      expect(lib.compactArray(lib.plain(items, 'name')).length).toEqual(3);
    });
    it("plain(name) to contain Utkin", function () {
      expect(lib.compactArray(lib.plain(items, 'name'))).toContain('Utkin');
    });
    it("plain(name) == plain", function () {
      expect(lib.compactArray(lib.plain(items, 'name'))).toEqual(plain);
    });
  });

  describe("sumBy checks", function () {
    it("sumBy() == 0", function () {
      expect(lib.sumBy(items)).toEqual(0);
    });
    it("sumBy(age) to be NaN", function () {
      expect(lib.sumBy(items, 'age')).toBeNaN();
    });
    it("sumBy(city) to be NaN", function () {
      expect(lib.sumBy(items, 'city')).toBeNaN();
    });
    it("sumBy(filterHaving(age)) == 98", function () {
      expect(lib.sumBy(lib.filterHaving(items, 'age'), 'age')).toEqual(98);
    });
  });

  describe("addProperty checks", function () {
    it("addProperty(items,undefined) length == 0", function () {
      expect(lib.filterHaving(lib.addProperty(items), 'zip').length).toEqual(0);
    });
    it("addProperty(items,newProperty) length == 4", function () {
      expect(lib.filterHaving(lib.addProperty(items, newProperty), 'zip').length).toEqual(4);
    });
    it("addProperty(items,newProperty) -> plain('zip') == ['680009','680009','680009','680009']", function () {
      expect(lib.plain(
        lib.filterHaving(
          lib.addProperty(items, newProperty),
          'zip'),
        'zip'))
      .toEqual(['680009','680009','680009','680009']);
    });
  });

});
<commit_msg>test: Add missing-argument tests<commit_after>if (!this.navigator) {
  var lib = require('../utils');
}

var items = [
  {
    name: 'Utkin',
    age: 35
  },
  {
    name: 'Ivanov'
  },
  {
    name: 'Petrov',
    age: 38
  },
  {
    age: 25,
    salary: 87000
  }
];

var plain = ['Utkin', 'Ivanov', 'Petrov'];
var newProperty = {
  city: 'Khabarovsk',
  zip: '680009'
};

describe("Check Utils.js", function () {
  beforeEach(function () {
    expect(items.length).toEqual(4);
    expect(lib.filterHaving(items, 'salary').length).toEqual(1);
  });

  describe("filterHaving checks", function () {
    it("filterHaving() on empty array length = 0", function () {
      expect(lib.filterHaving().length).toEqual(0);
    });
    it("filterHaving() length = 0", function () {
      expect(lib.filterHaving(items).length).toEqual(0);
    });
    it("filterHaving(xxx) length = 0", function () {
      expect(lib.filterHaving(items, 'xxx').length).toEqual(0);
    });
    it("filterHaving(age) length = 2", function () {
      expect(lib.filterHaving(items, 'age').length).toEqual(3);
    });
  });

  describe("extract checks", function () {
    it("extract(salary) without arguments length = 0", function () {
      expect(lib.filterHaving().length).toEqual(0);
    });
    it("extract(salary) with just a field argument length = 0", function () {
      expect(lib.filterHaving('salary').length).toEqual(0);
    });
    it("extract(salary) on empty array length = 0", function () {
      expect(lib.filterHaving([], 'salary').length).toEqual(0);
    });
    it("extract(salary) without field length = 0", function () {
      expect(lib.filterHaving([]).length).toEqual(0);
    });
    it("extract(salary) length = 1", function () {
      expect(lib.filterHaving(items, 'salary').length).toEqual(1);
    });
    it("extract(salary)[0] has salary defined", function () {
      expect(lib.filterHaving(items, 'salary')[0].salary).toBeDefined();
    });
    it("extract(salary)[0] has name undefined", function () {
      expect(lib.filterHaving(items, 'salary')[0].name).toBeUndefined();
    });
    it("extract(salary)[0].salary == 87000", function () {
      expect(lib.filterHaving(items, 'salary')[0].salary).toEqual(87000);
    });
  });

  describe("plain checks", function () {
    it("plain(name) on empty array length = 0", function () {
      expect(lib.plain().length).toEqual(0);
    });
    it("plain(name) length = 4", function () {
      expect(lib.plain(items, 'name').length).toEqual(4);
    });
    it("plain(name) to contain undefined", function () {
      expect(lib.plain(items, 'name')).toContain(undefined);
    });
    it("plain(name) not to contain null", function () {
      expect(lib.plain(items, 'name')).not.toContain(null);
    });
    it("plain(name) length = 3", function () {
      expect(lib.compactArray(lib.plain(items, 'name')).length).toEqual(3);
    });
    it("plain(name) to contain Utkin", function () {
      expect(lib.compactArray(lib.plain(items, 'name'))).toContain('Utkin');
    });
    it("plain(name) == plain", function () {
      expect(lib.compactArray(lib.plain(items, 'name'))).toEqual(plain);
    });
  });

  describe("sumBy checks", function () {
    it("sumBy() on empty array == 0", function () {
      expect(lib.sumBy()).toEqual(0);
    });
    it("sumBy() == 0", function () {
      expect(lib.sumBy(items)).toEqual(0);
    });
    it("sumBy(age) to be NaN", function () {
      expect(lib.sumBy(items, 'age')).toBeNaN();
    });
    it("sumBy(city) to be NaN", function () {
      expect(lib.sumBy(items, 'city')).toBeNaN();
    });
    it("sumBy(filterHaving(age)) == 98", function () {
      expect(lib.sumBy(lib.filterHaving(items, 'age'), 'age')).toEqual(98);
    });
  });

  describe("addProperty checks", function () {
    it("addProperty() on empty array length == 0", function () {
      expect(lib.filterHaving(lib.addProperty(), 'zip').length).toEqual(0);
    });
    it("addProperty(items,undefined) length == 0", function () {
      expect(lib.filterHaving(lib.addProperty(items), 'zip').length).toEqual(0);
    });
    it("addProperty(items,newProperty) length == 4", function () {
      expect(lib.filterHaving(lib.addProperty(items, newProperty), 'zip').length).toEqual(4);
    });
    it("addProperty(items,newProperty) -> plain('zip') == ['680009','680009','680009','680009']", function () {
      expect(lib.plain(
        lib.filterHaving(
          lib.addProperty(items, newProperty),
          'zip'),
        'zip'))
      .toEqual(['680009','680009','680009','680009']);
    });
  });

});
<|endoftext|>


<commit_before>// @flow

import moment from 'moment'
import React, {Component} from 'react'
import {
  Alert,
  Button,
  ButtonToolbar,
  Col,
  Glyphicon,
  Label,
  Panel,
  Row,
  Table
} from 'react-bootstrap'
import {browserHistory, Link} from 'react-router'

import * as gtfsPlusActions from '../actions/gtfsplus'
import {getGtfsPlusSpec} from '../../common/util/config'
import type {Props as ContainerProps} from '../containers/ActiveGtfsPlusVersionSummary'
import type {GtfsSpecTable} from '../../types'
import type {GtfsPlusReducerState, ManagerUserState} from '../../types/reducers'

type Issue = {
  description: string,
  fieldName: string,
  rowIndex: number,
  tableId: string
}

type Props = ContainerProps & {
  deleteGtfsPlusFeed: typeof gtfsPlusActions.deleteGtfsPlusFeed,
  downloadGtfsPlusFeed: typeof gtfsPlusActions.downloadGtfsPlusFeed,
  gtfsplus: GtfsPlusReducerState,
  issuesForTable: any,
  publishGtfsPlusFeed: typeof gtfsPlusActions.publishGtfsPlusFeed,
  user: ManagerUserState
}

type State = {
  expanded: boolean,
  tableExpanded: any
}

type IssueFilter = Issue => boolean

export default class GtfsPlusVersionSummary extends Component<Props, State> {
  state = {
    expanded: false,
    tableExpanded: {}
  }

  componentDidMount () {
    this.props.downloadGtfsPlusFeed(this.props.version.id)
  }

  isTableIncluded (tableId: string) {
    const {tableData} = this.props.gtfsplus
    if (!tableData) return null
    return tableId in tableData ? 'Yes' : 'No'
  }

  tableRecordCount (tableId: string): ?string {
    const {tableData} = this.props.gtfsplus
    if (!tableData) return null
    if (!(tableId in tableData)) return 'N/A'
    return tableData[tableId].length.toLocaleString()
  }

  validationIssueCount (tableId: string): ?string {
    const {issuesForTable} = this.props
    if (!issuesForTable) return null
    if (!(tableId in issuesForTable)) return 'None'
    return issuesForTable[tableId].length.toLocaleString()
  }

  _getTableLevelIssues = (tableId: string): ?Array<Issue> => {
    return this._getIssues(tableId, issue => issue.rowIndex === -1)
  }

  _getIssues = (tableId: string, filter: ?IssueFilter): ?Array<Issue> => {
    const {issuesForTable} = this.props
    if (!issuesForTable) return null
    if (!(tableId in issuesForTable)) return null

    // Filter table level issues or row issues using the specified filter.
    filter = filter || (() => true)
    const issues = issuesForTable[tableId].filter(filter)
    return (issues.length > 0 ? issues : null)
  }

  feedStatus () {
    const {timestamp} = this.props.gtfsplus
    if (!timestamp) return null
    if (!this._gtfsPlusIsEdited()) return GTFS+ data for this feed version has not been edited.
    const timestampMoment = moment(timestamp)
    const formatted = timestampMoment.format('MMM. DD, YYYY, h:MMa')
    const fromNow = timestampMoment.fromNow()
    return GTFS+ data updated {fromNow}.
  }

  _gtfsPlusIsEdited = () => (this.props.gtfsplus.timestamp !== this.props.version.fileTimestamp)

  _onClickEdit = () => {
    const {version} = this.props
    browserHistory.push(`/gtfsplus/${version.feedSource.id}/${version.id}`)
  }

  _onClickDelete = () => {
    if (window.confirm('Are you sure you want to delete the GTFS+ edits for this feed version? Note: this will not delete the feed version itself.')) {
      this.props.deleteGtfsPlusFeed(this.props.version.id)
    }
  }

  _onClickPublish = () => {
    this.props.publishGtfsPlusFeed(this.props.version)
    this.setState({ expanded: false })
  }

  _toggleExpanded = () => {
    const {downloadGtfsPlusFeed, version} = this.props
    const {expanded} = this.state
    if (!expanded) downloadGtfsPlusFeed(version.id)
    this.setState({ expanded: !expanded })
  }

  _toggleTableExpanded = (tableName: string): void => {
    const { tableExpanded } = this.state
    const newTableExpanded = Object.assign(tableExpanded)
    newTableExpanded[tableName] = !newTableExpanded[tableName]

    this.setState({ tableExpanded: newTableExpanded })
  }

  renderIssues = (table: GtfsSpecTable) => {
    const { tableExpanded } = this.state
    const isExpanded = tableExpanded[table.name]
    const issueCount = this.validationIssueCount(table.id)
    const tableLevelIssues = this._getTableLevelIssues(table.id)
    const allIssues = this._getIssues(table.id)
    allIssues && allIssues.sort(
      (issue1, issue2) => issue1.rowIndex - issue2.rowIndex
    )

    return (

          <Button
            bsSize='small'
            bsStyle='link'
            onClick={() => this._toggleTableExpanded(table.name)}
          >

              <Glyphicon
                glyph={isExpanded ? 'triangle-bottom' : 'triangle-right'}
                style={{marginRight: '0.5em'}} />

            {issueCount} validation {issueCount !== 1 ? 'issues' : 'issue' }
            {tableLevelIssues && issueCount && ` (${tableLevelIssues.length}/${issueCount} blocking)`}
          </Button>

          {isExpanded &&

                Line
                Column
                Issue

              {allIssues && allIssues.map((issue, index) =>

                  {issue.rowIndex + 2}	 {/* This is the line number in the file */}
                  {issue.fieldName}

                    {issue.description}
                    {' '}
                    {issue.rowIndex === -1 && BLOCKING}

              )}

}

    )
  }

  render () {
    const {
      gtfsplus,
      user,
      version,
      versions
    } = this.props
    const { feedSource } = version
    const editingIsDisabled = !user.permissions ||
      !user.permissions.hasFeedPermission(
        feedSource.organizationId,
        feedSource.projectId,
        feedSource.id,
        'edit-gtfs'
      )
    const publishingIsDisabled = !user.permissions ||
      !user.permissions.hasFeedPermission(
        feedSource.organizationId,
        feedSource.projectId,
        feedSource.id,
        'approve-gtfs'
      )
    const header = (

        <Glyphicon glyph='check' /> GTFS+ for this Version

    )

    const derivedVersions = versions.filter(v => v.originNamespace === version.namespace)
    return (
      <Panel header={header}>
        <Row>
          <Col xs={6}>
            <Alert>
              {this.feedStatus()}
              {derivedVersions.length > 0
                ?

                  Versions created from this version of GTFS+:

                    {derivedVersions.map(v => {
                      const url = `/feed/${v.feedSource.id}/version/${v.version}`
                      return (

                          <Link to={url}>
                            {v.name} (version {v.version})
                          </Link>

                      )
                    })}

                : this._gtfsPlusIsEdited() && gtfsplus.validation && gtfsplus.validation.issues.length === 0
                  ?
Note: Edits must be published as a new feed version.

                  : null

              }
            </Alert>
          </Col>
          <Col xs={6} style={{ textAlign: 'right' }}>
            <ButtonToolbar className='pull-right'>
              {this._gtfsPlusIsEdited()
                ? <Button
                  onClick={this._onClickDelete}>
                  <Glyphicon glyph='remove' /> Clear edits
                </Button>
                : null
              }
              <Button
                disabled={editingIsDisabled}
                bsStyle='primary'
                onClick={this._onClickEdit}>
                <Glyphicon glyph='edit' /> Edit GTFS+
              </Button>
              {this._gtfsPlusIsEdited()
                ? <Button
                  disabled={publishingIsDisabled}
                  bsStyle='primary'
                  onClick={this._onClickPublish}>
                  <Glyphicon glyph='upload' /> Create a New Version
                </Button>
                : null
              }
            </ButtonToolbar>
          </Col>
        </Row>
        <Row>
          <Col xs={12}>
            <Panel>

                    Table
                    Included?
                    Records
                    Validation Issues

                {/* FIXME: reinstate this
 after switching to React 16. */}
                {/**
                    * Change the behavior as follows:
                    * - Table-level issues are still critical and blocking and and displayed in red.
                    * - Per-row issues are still amber warnings and non-blocking,
                    *   but will now be displayed individually instead of being aggregated.
                    *   Maybe only display the first 25 issues to avoid long rendering times???
                    * - Issues are displayed on a full-width sub-table for better readability,
                    *   in the same "row" as the issue summary.
                    * - Tables are sorted alphabetically.
                    */}
                {getGtfsPlusSpec()
                  .sort((table1, table2) => table1.name.localeCompare(table2.name))
                  .map((table, index) => {
                    const issueCount = this.validationIssueCount(table.id)
                    const tableLevelIssues = this._getTableLevelIssues(table.id)
                    const hasIssues = +issueCount > 0
                    const className = tableLevelIssues
                      ? 'danger'
                      : (hasIssues ? 'warning' : '')

                    return (
                      // FIXME: Use <React.Fragment key={index}> (React 16+ only.)

                            {table.name}

                          {this.isTableIncluded(table.id)}
                          {this.tableRecordCount(table.id)}
                          {issueCount}

                        {hasIssues && (

                              {this.renderIssues(table)}

                        )}

                      // </React.Fragment>
                    )
                  })}
                {/*
 */}

            </Panel>
          </Col>
        </Row>
      </Panel>
    )
  }
}
<commit_msg>improvement(GtfsPlusVersionSummary): Remove top boder in issues cell. Remove issue table botton marg<commit_after>// @flow

import moment from 'moment'
import React, {Component} from 'react'
import {
  Alert,
  Button,
  ButtonToolbar,
  Col,
  Glyphicon,
  Label,
  Panel,
  Row,
  Table
} from 'react-bootstrap'
import {browserHistory, Link} from 'react-router'

import * as gtfsPlusActions from '../actions/gtfsplus'
import {getGtfsPlusSpec} from '../../common/util/config'
import type {Props as ContainerProps} from '../containers/ActiveGtfsPlusVersionSummary'
import type {GtfsSpecTable} from '../../types'
import type {GtfsPlusReducerState, ManagerUserState} from '../../types/reducers'

type Issue = {
  description: string,
  fieldName: string,
  rowIndex: number,
  tableId: string
}

type Props = ContainerProps & {
  deleteGtfsPlusFeed: typeof gtfsPlusActions.deleteGtfsPlusFeed,
  downloadGtfsPlusFeed: typeof gtfsPlusActions.downloadGtfsPlusFeed,
  gtfsplus: GtfsPlusReducerState,
  issuesForTable: any,
  publishGtfsPlusFeed: typeof gtfsPlusActions.publishGtfsPlusFeed,
  user: ManagerUserState
}

type State = {
  expanded: boolean,
  tableExpanded: any
}

type IssueFilter = Issue => boolean

export default class GtfsPlusVersionSummary extends Component<Props, State> {
  state = {
    expanded: false,
    tableExpanded: {}
  }

  componentDidMount () {
    this.props.downloadGtfsPlusFeed(this.props.version.id)
  }

  isTableIncluded (tableId: string) {
    const {tableData} = this.props.gtfsplus
    if (!tableData) return null
    return tableId in tableData ? 'Yes' : 'No'
  }

  tableRecordCount (tableId: string): ?string {
    const {tableData} = this.props.gtfsplus
    if (!tableData) return null
    if (!(tableId in tableData)) return 'N/A'
    return tableData[tableId].length.toLocaleString()
  }

  validationIssueCount (tableId: string): ?string {
    const {issuesForTable} = this.props
    if (!issuesForTable) return null
    if (!(tableId in issuesForTable)) return 'None'
    return issuesForTable[tableId].length.toLocaleString()
  }

  _getTableLevelIssues = (tableId: string): ?Array<Issue> => {
    return this._getIssues(tableId, issue => issue.rowIndex === -1)
  }

  _getIssues = (tableId: string, filter: ?IssueFilter): ?Array<Issue> => {
    const {issuesForTable} = this.props
    if (!issuesForTable) return null
    if (!(tableId in issuesForTable)) return null

    // Filter table level issues or row issues using the specified filter.
    filter = filter || (() => true)
    const issues = issuesForTable[tableId].filter(filter)
    return (issues.length > 0 ? issues : null)
  }

  feedStatus () {
    const {timestamp} = this.props.gtfsplus
    if (!timestamp) return null
    if (!this._gtfsPlusIsEdited()) return GTFS+ data for this feed version has not been edited.
    const timestampMoment = moment(timestamp)
    const formatted = timestampMoment.format('MMM. DD, YYYY, h:MMa')
    const fromNow = timestampMoment.fromNow()
    return GTFS+ data updated {fromNow}.
  }

  _gtfsPlusIsEdited = () => (this.props.gtfsplus.timestamp !== this.props.version.fileTimestamp)

  _onClickEdit = () => {
    const {version} = this.props
    browserHistory.push(`/gtfsplus/${version.feedSource.id}/${version.id}`)
  }

  _onClickDelete = () => {
    if (window.confirm('Are you sure you want to delete the GTFS+ edits for this feed version? Note: this will not delete the feed version itself.')) {
      this.props.deleteGtfsPlusFeed(this.props.version.id)
    }
  }

  _onClickPublish = () => {
    this.props.publishGtfsPlusFeed(this.props.version)
    this.setState({ expanded: false })
  }

  _toggleExpanded = () => {
    const {downloadGtfsPlusFeed, version} = this.props
    const {expanded} = this.state
    if (!expanded) downloadGtfsPlusFeed(version.id)
    this.setState({ expanded: !expanded })
  }

  _toggleTableExpanded = (tableName: string): void => {
    const { tableExpanded } = this.state
    const newTableExpanded = Object.assign(tableExpanded)
    newTableExpanded[tableName] = !newTableExpanded[tableName]

    this.setState({ tableExpanded: newTableExpanded })
  }

  renderIssues = (table: GtfsSpecTable) => {
    const { tableExpanded } = this.state
    const isExpanded = tableExpanded[table.name]
    const issueCount = this.validationIssueCount(table.id)
    const tableLevelIssues = this._getTableLevelIssues(table.id)
    const allIssues = this._getIssues(table.id)
    allIssues && allIssues.sort(
      (issue1, issue2) => issue1.rowIndex - issue2.rowIndex
    )

    return (

          <Button
            bsSize='small'
            bsStyle='link'
            onClick={() => this._toggleTableExpanded(table.name)}
          >

              <Glyphicon
                glyph={isExpanded ? 'triangle-bottom' : 'triangle-right'}
                style={{marginRight: '0.5em'}} />

            {issueCount} validation {issueCount !== 1 ? 'issues' : 'issue' }
            {tableLevelIssues && issueCount && ` (${tableLevelIssues.length}/${issueCount} blocking)`}
          </Button>

          {isExpanded &&

                Line
                Column
                Issue

              {allIssues && allIssues.map((issue, index) =>

                  {issue.rowIndex + 2}	 {/* This is the line number in the file */}
                  {issue.fieldName}

                    {issue.description}
                    {' '}
                    {issue.rowIndex === -1 && BLOCKING}

              )}

}

    )
  }

  render () {
    const {
      gtfsplus,
      user,
      version,
      versions
    } = this.props
    const { feedSource } = version
    const editingIsDisabled = !user.permissions ||
      !user.permissions.hasFeedPermission(
        feedSource.organizationId,
        feedSource.projectId,
        feedSource.id,
        'edit-gtfs'
      )
    const publishingIsDisabled = !user.permissions ||
      !user.permissions.hasFeedPermission(
        feedSource.organizationId,
        feedSource.projectId,
        feedSource.id,
        'approve-gtfs'
      )
    const header = (

        <Glyphicon glyph='check' /> GTFS+ for this Version

    )

    const derivedVersions = versions.filter(v => v.originNamespace === version.namespace)
    return (
      <Panel header={header}>
        <Row>
          <Col xs={6}>
            <Alert>
              {this.feedStatus()}
              {derivedVersions.length > 0
                ?

                  Versions created from this version of GTFS+:

                    {derivedVersions.map(v => {
                      const url = `/feed/${v.feedSource.id}/version/${v.version}`
                      return (

                          <Link to={url}>
                            {v.name} (version {v.version})
                          </Link>

                      )
                    })}

                : this._gtfsPlusIsEdited() && gtfsplus.validation && gtfsplus.validation.issues.length === 0
                  ?
Note: Edits must be published as a new feed version.

                  : null

              }
            </Alert>
          </Col>
          <Col xs={6} style={{ textAlign: 'right' }}>
            <ButtonToolbar className='pull-right'>
              {this._gtfsPlusIsEdited()
                ? <Button
                  onClick={this._onClickDelete}>
                  <Glyphicon glyph='remove' /> Clear edits
                </Button>
                : null
              }
              <Button
                disabled={editingIsDisabled}
                bsStyle='primary'
                onClick={this._onClickEdit}>
                <Glyphicon glyph='edit' /> Edit GTFS+
              </Button>
              {this._gtfsPlusIsEdited()
                ? <Button
                  disabled={publishingIsDisabled}
                  bsStyle='primary'
                  onClick={this._onClickPublish}>
                  <Glyphicon glyph='upload' /> Create a New Version
                </Button>
                : null
              }
            </ButtonToolbar>
          </Col>
        </Row>
        <Row>
          <Col xs={12}>
            <Panel>

                    Table
                    Included?
                    Records
                    Validation Issues

                {/* FIXME: reinstate this
 after switching to React 16. */}
                {/**
                    * Change the behavior as follows:
                    * - Table-level issues are still critical and blocking and and displayed in red.
                    * - Per-row issues are still amber warnings and non-blocking,
                    *   but will now be displayed individually instead of being aggregated.
                    *   Maybe only display the first 25 issues to avoid long rendering times???
                    * - Issues are displayed on a full-width sub-table for better readability,
                    *   in the same "row" as the issue summary.
                    * - Tables are sorted alphabetically.
                    */}
                {getGtfsPlusSpec()
                  .sort((table1, table2) => table1.name.localeCompare(table2.name))
                  .map((table, index) => {
                    const issueCount = this.validationIssueCount(table.id)
                    const tableLevelIssues = this._getTableLevelIssues(table.id)
                    const hasIssues = +issueCount > 0
                    const className = tableLevelIssues
                      ? 'danger'
                      : (hasIssues ? 'warning' : '')

                    return (
                      // FIXME: Use <React.Fragment key={index}> (React 16+ only.)

                          {table.name}
                          {this.isTableIncluded(table.id)}
                          {this.tableRecordCount(table.id)}
                          {issueCount}

                        {hasIssues && (

                              {this.renderIssues(table)}

                        )}

                      // </React.Fragment>
                    )
                  })}
                {/*
 */}

            </Panel>
          </Col>
        </Row>
      </Panel>
    )
  }
}
<|endoftext|>


<commit_before>apos.define('apostrophe-pieces-editor', {
  extend: 'apostrophe-modal',
  source: 'editor',
  construct: function(self, options) {
    console.log(options);
    self.schema = options.schema;
    self._id = options._id;

    options.source = 'editor';

    self.beforeShow = function(callback) {
      self.$form = self.$el.find('[data-form]');
      if (self._id) {
        self.edit(self._id, callback);
      } else {
        self.create(callback);
      }
    };

    self.edit = function(_id, callback) {
      return self.open('retrieve', { _id: _id }, callback);
    };

    self.create = function(callback) {
      console.log('IN CREATE');
      var piece = apos.schemas.newInstance(self.schema);
      return self.populate(piece, callback);
    };

    self.open = function(verb, data, callback) {
      console.log('IN OPEN');
      console.log(arguments);
      var url = self.options.action + '/' + verb;
      $.jsonCall(url, data, function(result) {
        if (result.status === 'notfound') {
          alert('That item does not exist.');
          return callback('error');
        } else if (result.status !== 'ok') {
          alert('An error occurred. Please try again.');
          return callback('error');
        }
        self._id = result.data._id;
        return self.populate(result.data, callback);
      }, function() {
        alert('An error occurred. Please try again.');
        return callback('network');
      });
    };

    self.populate = function(piece, callback) {
      return async.series({
        beforePopulate: function(callback) {
          return self.beforePopulate(piece, callback);
        },
        populate: function(callback) {
          return apos.schemas.populate(self.$form, self.schema, piece, callback);
        },
        afterPopulate: function(callback) {
          return self.afterPopulate(piece, callback);
        }
      }, callback);
    };

    self.beforePopulate = function(piece, callback) {
      return setImmediate(callback);
    };

    self.afterPopulate = function(piece, callback) {
      return setImmediate(callback);
    };

    self.save = function(callback) {
      var piece = {
        _id: self._id
      };

      return async.series({
        before: function(callback) {
          return self.beforeConvert(piece, callback);
        },
        convert: function(callback) {
          return apos.schemas.convert(self.$form, self.schema, piece, callback);
        },
        after: function(callback) {
          return self.afterConvert(piece, callback);
        }
      }, function(err) {
        if (err) {
          console.log(err);
          return callback(err);
        }
        var verb = piece._id ? 'update' : 'insert';
        $.jsonCall(self.options.action + '/' + verb, piece, function(result) {
          if (result.status !== 'ok') {
            alert('An error occurred. Please try again.');
            return callback('error');
          }
          apos.change(result.data);
          return callback(null);
        }, function() {
          alert('An error occurred. Please try again');
          return callback(err);
        });
      });
    };

    self.beforeConvert = function(piece, callback) {
      return setImmediate(callback);
    };

    self.afterConvert = function(piece, callback) {
      return setImmediate(callback);
    };

  }
});
<commit_msg>hook in trashing to editor modal for pieces<commit_after>apos.define('apostrophe-pieces-editor', {
  extend: 'apostrophe-modal',
  source: 'editor',
  construct: function(self, options) {
    console.log(options);
    self.schema = options.schema;
    self._id = options._id;

    options.source = 'editor';

    self.beforeShow = function(callback) {
      self.$form = self.$el.find('[data-form]');
      if (self._id) {
        self.edit(self._id, callback);
      } else {
        self.create(callback);
      }
      self.action('trash', function($el) {
        self.trash($el);
      });
    };

    self.edit = function(_id, callback) {
      return self.open('retrieve', { _id: _id }, callback);
    };

    self.create = function(callback) {
      console.log('IN CREATE');
      var piece = apos.schemas.newInstance(self.schema);
      return self.populate(piece, callback);
    };

    self.open = function(verb, data, callback) {
      console.log('IN OPEN');
      console.log(arguments);
      var url = self.options.action + '/' + verb;
      $.jsonCall(url, data, function(result) {
        if (result.status === 'notfound') {
          alert('That item does not exist.');
          return callback('error');
        } else if (result.status !== 'ok') {
          alert('An error occurred. Please try again.');
          return callback('error');
        }
        self._id = result.data._id;
        return self.populate(result.data, callback);
      }, function() {
        alert('An error occurred. Please try again.');
        return callback('network');
      });
    };

    self.populate = function(piece, callback) {
      return async.series({
        beforePopulate: function(callback) {
          return self.beforePopulate(piece, callback);
        },
        populate: function(callback) {
          return apos.schemas.populate(self.$form, self.schema, piece, callback);
        },
        afterPopulate: function(callback) {
          return self.afterPopulate(piece, callback);
        }
      }, callback);
    };

    self.beforePopulate = function(piece, callback) {
      return setImmediate(callback);
    };

    self.afterPopulate = function(piece, callback) {
      return setImmediate(callback);
    };

    self.save = function(callback) {
      var piece = {
        _id: self._id
      };

      return async.series({
        before: function(callback) {
          return self.beforeConvert(piece, callback);
        },
        convert: function(callback) {
          return apos.schemas.convert(self.$form, self.schema, piece, callback);
        },
        after: function(callback) {
          return self.afterConvert(piece, callback);
        }
      }, function(err) {
        if (err) {
          console.log(err);
          return callback(err);
        }
        var verb = piece._id ? 'update' : 'insert';
        $.jsonCall(self.options.action + '/' + verb, piece, function(result) {
          if (result.status !== 'ok') {
            alert('An error occurred. Please try again.');
            return callback('error');
          }
          apos.change(result.data);
          return callback(null);
        }, function() {
          alert('An error occurred. Please try again');
          return callback(err);
        });
      });
    };

    self.beforeConvert = function(piece, callback) {
      return setImmediate(callback);
    };

    self.afterConvert = function(piece, callback) {
      return setImmediate(callback);
    };

    self.trash = function($el, next) {
      if (self.trashing || !confirm("Are you sure you want to trash this " + self.options.label + "?")) {
        return;
      }

      var piece = {
        _id: self._id
      };

      self.trashing = true;
      $el.addClass('apos-busy');

      $.jsonCall(self.options.action + '/trash', piece, function(result) {
        self.trashing = false;
        $el.removeClass('apos-busy');
        if (result.status !== 'ok') {
          alert('An error occurred. Please try again.');
          if (next) {
            return next('error');
          }
          return;
        }
        apos.modalSupport.closeTopModal();
        if (next) {
          return next(null);
        }
        return;
      }, function() {
        self.trashing = false;
        $el.removeClass('apos-busy');
        alert('An error occurred. Please try again');
        if (next) {
          return next(err);
        }
        return;
      });
    };
  }
});
<|endoftext|>


<commit_before>"use strict";

define(['logManager',
    'clientUtil',
    'js/DragDrop/DragHelper',
    'js/Widgets/DiagramDesigner/DiagramDesignerWidget',
    'js/Controls/iCheckBox',
    './MetaEditorPointerNamesDialog',
    'css!/css/Widgets/MetaEditor/MetaEditorWidget'], function (logManager,
                                                             clientUtil,
                                                             DragHelper,
                                                             DiagramDesignerWidget,
                                                             iCheckBox,
                                                             MetaEditorPointerNamesDialog) {

    var MetaEditorWidget,
        __parent__ = DiagramDesignerWidget,
        __parent_proto__ = DiagramDesignerWidget.prototype;

    MetaEditorWidget = function (container, params) {
        params = params || {};
        params.loggerName = "MetaEditorWidget";

        //disable line style parameter controls in toolbar
        params.lineStyleControls = false;

        params.tabsEnabled = true;
        params.addTabs = true;
        params.deleteTabs = true;
        params.reorderTabs = true;

        __parent__.call(this, container, params);

        this.logger.debug("MetaEditorWidget ctor");
    };

    _.extend(MetaEditorWidget.prototype, DiagramDesignerWidget.prototype);

    MetaEditorWidget.prototype._initializeUI = function (containerElement) {
        __parent_proto__._initializeUI.apply(this, arguments);
        this.logger.debug("MetaEditorWidget._initializeUI");

        //disable connection to a connection
        this._connectToConnection = false;

        this._initializeFilterPanel();
    };

    MetaEditorWidget.prototype._afterManagersInitialized = function () {
        //turn off item rotation
        this.enableRotate(false);
    };

    MetaEditorWidget.prototype._initializeFilterPanel = function () {
        /**** create FILTER PANEL ****/
        this.$filterPanel = $('
', {
            'class': 'filterPanel'
        });

        this.$filterPanel.html('
FILTER

');

        this.$filterHeader = this.$filterPanel.find('.header');
        this.$filterUl = this.$filterPanel.find('ul.body');

        this.$el.parent().append(this.$filterPanel);

        this._filterCheckboxes = {};
    };

    MetaEditorWidget.prototype._checkChanged = function (value, isChecked) {
        this._refreshHeaderText();
        this.logger.debug("CheckBox checkChanged: " + value + ", checked: " + isChecked);
        this.onCheckChanged(value, isChecked);
    };

    MetaEditorWidget.prototype.onCheckChanged = function (value, isChecked) {
        this.logger.warning('MetaEditorWidget.onCheckChanged(value, isChecked) is not overridden!');
    };

    MetaEditorWidget.prototype.addFilterItem = function (text, value, iconEl) {
        var item = $('
', {
                'class': 'filterItem'
            }),
            checkBox,
            self = this;

        checkBox = new iCheckBox({
            "checkChangedFn": function (data, isChecked) {
                self._checkChanged(value, isChecked);
            }});

        item.append(iconEl.addClass('inline'));
        item.append(text);
        item.append(checkBox.el);

        this.$filterUl.append(item);

        this._refreshHeaderText();

        this._filterCheckboxes[value] = checkBox;

        return item;
    };

    MetaEditorWidget.prototype._refreshHeaderText = function () {
        var all = this.$filterUl.find('.iCheckBox').length,
            on = this.$filterUl.find('.iCheckBox.checked').length;

        this.$filterHeader.html('FILTER' + (all === on ? '' : ' *'));
    };

    MetaEditorWidget.prototype.selectNewPointerName = function (existingPointerNames, notAllowedPointerNames, isSet, callBack) {
       new MetaEditorPointerNamesDialog().show(existingPointerNames, notAllowedPointerNames, isSet, callBack);
    };

    MetaEditorWidget.prototype.setFilterChecked = function (value) {
        if (this._filterCheckboxes[value] && !this._filterCheckboxes[value].isChecked()) {
            this._filterCheckboxes[value].setChecked(true);
        }
    };

    MetaEditorWidget.prototype.getDragEffects = function (/*selectedElements, event*/) {
        //the only drag is a MOVE
        return [DragHelper.DRAG_EFFECTS.DRAG_MOVE];
    };

    /* OVERWRITE DiagramDesignerWidget.prototype._dragHelper */
    MetaEditorWidget.prototype._dragHelper = function (el, event, dragInfo) {
        var helperEl = DiagramDesignerWidget.prototype._dragHelper.apply(this, [el, event, dragInfo]);

        //clear out default 'Move' text from helperEl
        helperEl.html('');

        return helperEl;
    };

    return MetaEditorWidget;
});<commit_msg>refactor<commit_after>"use strict";

define(['logManager',
    'clientUtil',
    'js/DragDrop/DragHelper',
    'js/Widgets/DiagramDesigner/DiagramDesignerWidget',
    'js/Controls/iCheckBox',
    './MetaEditorPointerNamesDialog',
    'css!/css/Widgets/MetaEditor/MetaEditorWidget'], function (logManager,
                                                             clientUtil,
                                                             DragHelper,
                                                             DiagramDesignerWidget,
                                                             iCheckBox,
                                                             MetaEditorPointerNamesDialog) {

    var MetaEditorWidget,
        __parent__ = DiagramDesignerWidget,
        __parent_proto__ = DiagramDesignerWidget.prototype;

    MetaEditorWidget = function (container, params) {
        params = params || {};
        params.loggerName = "MetaEditorWidget";

        //disable line style parameter controls in toolbar
        params.lineStyleControls = false;

        params.tabsEnabled = true;
        params.addTabs = true;
        params.deleteTabs = true;
        params.reorderTabs = true;

        __parent__.call(this, container, params);

        this.logger.debug("MetaEditorWidget ctor");
    };

    _.extend(MetaEditorWidget.prototype, DiagramDesignerWidget.prototype);

    MetaEditorWidget.prototype._initializeUI = function (containerElement) {
        __parent_proto__._initializeUI.apply(this, arguments);
        this.logger.debug("MetaEditorWidget._initializeUI");

        //disable connection to a connection
        this._connectToConnection = false;

        this._initializeFilterPanel();
    };

    MetaEditorWidget.prototype._afterManagersInitialized = function () {
        //turn off item rotation
        this.enableRotate(false);
    };

    MetaEditorWidget.prototype._initializeFilterPanel = function () {
        /**** create FILTER PANEL ****/
        this.$filterPanel = $('
', {
            'class': 'filterPanel'
        });

        this.$filterPanel.html('
FILTER

');

        this.$filterHeader = this.$filterPanel.find('.header');
        this.$filterUl = this.$filterPanel.find('ul.body');

        this.$el.parent().append(this.$filterPanel);

        this._filterCheckboxes = {};
    };

    MetaEditorWidget.prototype._checkChanged = function (value, isChecked) {
        this._refreshHeaderText();
        this.logger.debug("CheckBox checkChanged: " + value + ", checked: " + isChecked);
        this.onCheckChanged(value, isChecked);
    };

    MetaEditorWidget.prototype.onCheckChanged = function (value, isChecked) {
        this.logger.warning('MetaEditorWidget.onCheckChanged(value, isChecked) is not overridden!');
    };

    MetaEditorWidget.prototype.addFilterItem = function (text, value, iconEl) {
        var item = $('
', {
                'class': 'filterItem'
            }),
            checkBox,
            self = this;

        checkBox = new iCheckBox({
            "checkChangedFn": function (data, isChecked) {
                self._checkChanged(value, isChecked);
            }});

        item.append(iconEl.addClass('inline'));
        item.append(text);
        item.append(checkBox.el);

        this.$filterUl.append(item);

        this._refreshHeaderText();

        this._filterCheckboxes[value] = checkBox;

        return item;
    };

    MetaEditorWidget.prototype._refreshHeaderText = function () {
        var all = this.$filterUl.find('.iCheckBox').length,
            on = this.$filterUl.find('.iCheckBox.checked').length;

        this.$filterHeader.html('FILTER' + (all === on ? '' : ' *'));
    };

    MetaEditorWidget.prototype.selectNewPointerName = function (existingPointerNames, notAllowedPointerNames, isSet, callBack) {
       new MetaEditorPointerNamesDialog().show(existingPointerNames, notAllowedPointerNames, isSet, callBack);
    };

    MetaEditorWidget.prototype.setFilterChecked = function (value) {
        if (this._filterCheckboxes[value] && !this._filterCheckboxes[value].isChecked()) {
            this._filterCheckboxes[value].setChecked(true);
        }
    };

    MetaEditorWidget.prototype.getDragEffects = function (/*selectedElements, event*/) {
        //the only drag is a MOVE
        return [DragHelper.DRAG_EFFECTS.DRAG_MOVE];
    };

    /* OVERWRITE DiagramDesignerWidget.prototype._dragHelper */
    MetaEditorWidget.prototype._dragHelper = function (el, event, dragInfo) {
        var helperEl = DiagramDesignerWidget.prototype._dragHelper.apply(this, [el, event, dragInfo]);

        //clear out default 'Move' text from helperEl
        helperEl.empty();

        return helperEl;
    };

    return MetaEditorWidget;
});<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import classnames from 'classnames'
import tinycolor from 'tinycolor2'
import * as formatNumberHelper from '~client/utils/format-number-helper'

if (require('exenv').canUseDOM) require('./progress.scss')

const colorStrategy = color => tinycolor(color).isDark() ? '#FFFFFF' : '#333333'

const Progress = ({
  className,
  value,
  max,
  valueTopLeft,
  valueTopRight,
  valueBottomLeft,
  valueBottomRight,
  fillColor
}) => (

{valueTopLeft}

{valueTopRight}

      <div className='progress-value'
        style={{
          backgroundColor: fillColor,
          width: `${value > 100 ? 100 : value}%`
        }}
      >
        {value > 5 && (

            {formatNumberHelper.number(value).slice(0, 2)}%

        )}

{valueBottomLeft}

{valueBottomRight}

)

Progress.propTypes = {
  className: PropTypes.string,
  value: PropTypes.number.isRquired,
  max: PropTypes.number,
  valueTopLeft: PropTypes.node,
  valueTopRight: PropTypes.node,
  valueBottomLeft: PropTypes.node,
  valueBottomRight: PropTypes.node,
  fillColor: PropTypes.string
}

Progress.defaultProps = {
  max: 100,
  fillColor: 'hsl(171, 100%, 41%)'
}

export default Progress
<commit_msg>fix(donation-widget) progress bar percentage as int #832<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import classnames from 'classnames'
import tinycolor from 'tinycolor2'
import * as formatNumberHelper from '~client/utils/format-number-helper'

if (require('exenv').canUseDOM) require('./progress.scss')

const colorStrategy = color => tinycolor(color).isDark() ? '#FFFFFF' : '#333333'

const Progress = ({
  className,
  value,
  max,
  valueTopLeft,
  valueTopRight,
  valueBottomLeft,
  valueBottomRight,
  fillColor
}) => (

{valueTopLeft}

{valueTopRight}

      <div className='progress-value'
        style={{
          backgroundColor: fillColor,
          width: `${value > 100 ? 100 : value}%`
        }}
      >
        {value > 5 && (

            {formatNumberHelper.number(value).split(',')[0]}%

        )}

{valueBottomLeft}

{valueBottomRight}

)

Progress.propTypes = {
  className: PropTypes.string,
  value: PropTypes.number.isRquired,
  max: PropTypes.number,
  valueTopLeft: PropTypes.node,
  valueTopRight: PropTypes.node,
  valueBottomLeft: PropTypes.node,
  valueBottomRight: PropTypes.node,
  fillColor: PropTypes.string
}

Progress.defaultProps = {
  max: 100,
  fillColor: 'hsl(171, 100%, 41%)'
}

export default Progress
<|endoftext|>


<commit_before>import React from 'react';

import CampaignTable from '../CampaignTable';

class CampaignOverview extends React.Component {
  constructor(props) {
    super(props);

    this.state = { search: '' };

    this.onChange = this.onChange.bind(this);
  }

  /**
   * Update state when search term changes.
   *
   * @param {*} event
   */
  onChange(event) {
    this.setState({ search: event.target.value });
  }

  /**
   * Filter the given campaigns by current search term.
   *
   * @return {Array}
   */
  filterCampaigns(campaigns) {
    const search = this.state.search.toLowerCase();

    return campaigns.filter(campaign => {
      if (this.state.search === '') {
        return true;
      }

      const matchesId = campaign.id.toString().includes(search);

      const matchesTitle = campaign.internal_title
        .toLowerCase()
        .includes(search);

      const matchesCause = campaign.cause.some(cause =>
        cause.toLowerCase().includes(search),
      );

      return matchesId || matchesTitle || matchesCause;
    });
  }

  /**
   * Render a tabled list of campaigns or a placeholder message if there are none to display.
   *
   * @return {Object}
   */
  renderCampaignsTable(campaigns) {
    return (

        {campaigns ? (
          <CampaignTable campaigns={this.filterCampaigns(campaigns)} />
        ) : (

No campaigns to display!

        )}

    );
  }

  /**
   * Render the campaign overview!
   *
   * @return {React.Element}
   */
  render() {
    return (

          <input
            type="text"
            className="text-field -search"
            placeholder="Filter by campaign ID, name, cause..."
            onChange={this.onChange}
          />

Pending Review

            These campaigns are currently active & have posts pending
            review:

        {this.renderCampaignsTable(this.props.pending)}

Everything Else

These campaigns are either closed or have no pending posts:

        {this.renderCampaignsTable(this.props.etc)}

    );
  }
}

export default CampaignOverview;
<commit_msg>Add param definition to docblock for filterCampaigns method<commit_after>import React from 'react';

import CampaignTable from '../CampaignTable';

class CampaignOverview extends React.Component {
  constructor(props) {
    super(props);

    this.state = { search: '' };

    this.onChange = this.onChange.bind(this);
  }

  /**
   * Update state when search term changes.
   *
   * @param {*} event
   */
  onChange(event) {
    this.setState({ search: event.target.value });
  }

  /**
   * Filter the given campaigns by current search term.
   *
   * @param  {Array|Null} campaigns
   * @return {Array}
   */
  filterCampaigns(campaigns) {
    const search = this.state.search.toLowerCase();

    return campaigns.filter(campaign => {
      if (this.state.search === '') {
        return true;
      }

      const matchesId = campaign.id.toString().includes(search);

      const matchesTitle = campaign.internal_title
        .toLowerCase()
        .includes(search);

      const matchesCause = campaign.cause.some(cause =>
        cause.toLowerCase().includes(search),
      );

      return matchesId || matchesTitle || matchesCause;
    });
  }

  /**
   * Render a tabled list of campaigns or a placeholder message if there are none to display.
   *
   * @return {Object}
   */
  renderCampaignsTable(campaigns) {
    return (

        {campaigns ? (
          <CampaignTable campaigns={this.filterCampaigns(campaigns)} />
        ) : (

No campaigns to display!

        )}

    );
  }

  /**
   * Render the campaign overview!
   *
   * @return {React.Element}
   */
  render() {
    return (

          <input
            type="text"
            className="text-field -search"
            placeholder="Filter by campaign ID, name, cause..."
            onChange={this.onChange}
          />

Pending Review

            These campaigns are currently active & have posts pending
            review:

        {this.renderCampaignsTable(this.props.pending)}

Everything Else

These campaigns are either closed or have no pending posts:

        {this.renderCampaignsTable(this.props.etc)}

    );
  }
}

export default CampaignOverview;
<|endoftext|>


<commit_before>var is = require('../lib/is.js');
var expect = require('chai').expect;

describe('Smoke tests', function () {

    it('can validate an object against a complicated schema', function () {
        var validate = is({
            a: is(Number, String),
            b: {
                c: {
                    d: {
                        e: is.ArrayOf(Date)
                    },
                    f: is.ObjectOf([Number, Number])
                },
                g: [Boolean, Boolean]
            },
            h: is.ArrayOf({ foo: /bar/})
        });

        expect(validate({
            a: 10,
            b: {
                c: {
                    d: {
                        e: [new Date(), new Date(), new Date()]
                    },
                    f: {
                        x: [10, 20],
                        y: [10, 20]
                    }
                },
                g: [true, true]
            },
            h: [
                {foo: 'bar'},
                {foo: 'foobar'},
                {foo: 'foobarbaz', xxx: 'yyy'}
            ]
        })).to.be.true;

        expect(validate({
            a: 10,
            b: {
                c: {
                    d: {
                        e: [new Date(), new Date(), new Date()]
                    },
                    f: {
                        x: [10, 20],
                        y: [10, '20'] // <-- ERROR
                    }
                },
                g: [true, true]
            },
            h: [
                {foo: 'bar'},
                {foo: 'foobar'},
                {foo: 'foobarbaz', xxx: 'yyy'}
            ]
        })).to.be.false;
    });

    it('can validate an array against a complex schema', function () {
        var validate = is.ArrayOf({
            x: String,
            y: is.ArrayOf(Number),
            z: is.ObjectOf([String, String])
        });

        expect(validate([
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}},
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}}
        ])).to.be.true;

        expect(validate([
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}},
            {x: 'foo', y: [1, 'Error!', 3], z: {a: ['x', 'y'], b: ['x', 'y']}}
        ])).to.be.false;
    });
});<commit_msg>Rewrites smoke tests descriptions<commit_after>var is = require('../lib/is.js');
var expect = require('chai').expect;

describe('Smoke tests', function () {

    it('can validate complex objects', function () {
        var validate = is({
            a: is(Number, String),
            b: {
                c: {
                    d: {
                        e: is.ArrayOf(Date)
                    },
                    f: is.ObjectOf([Number, Number])
                },
                g: [Boolean, Boolean]
            },
            h: is.ArrayOf({ foo: /bar/})
        });

        expect(validate({
            a: 10,
            b: {
                c: {
                    d: {
                        e: [new Date(), new Date(), new Date()]
                    },
                    f: {
                        x: [10, 20],
                        y: [10, 20]
                    }
                },
                g: [true, true]
            },
            h: [
                {foo: 'bar'},
                {foo: 'foobar'},
                {foo: 'foobarbaz', xxx: 'yyy'}
            ]
        })).to.be.true;

        expect(validate({
            a: 10,
            b: {
                c: {
                    d: {
                        e: [new Date(), new Date(), new Date()]
                    },
                    f: {
                        x: [10, 20],
                        y: [10, '20'] // <-- ERROR
                    }
                },
                g: [true, true]
            },
            h: [
                {foo: 'bar'},
                {foo: 'foobar'},
                {foo: 'foobarbaz', xxx: 'yyy'}
            ]
        })).to.be.false;
    });

    it('can validate complex arrays', function () {
        var validate = is.ArrayOf({
            x: String,
            y: is.ArrayOf(Number),
            z: is.ObjectOf([String, String])
        });

        expect(validate([
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}},
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}}
        ])).to.be.true;

        expect(validate([
            {x: 'foo', y: [1, 2, 3], z: {a: ['x', 'y'], b: ['x', 'y']}},
            {x: 'foo', y: [1, 'Error!', 3], z: {a: ['x', 'y'], b: ['x', 'y']}}
        ])).to.be.false;
    });
});<|endoftext|>


<commit_before>const
    path = require('path'),
    fs = require('fs'),
    expect = require("chai").expect,
    execFile = require('child_process').execFile,
    cproc = require('child_process'),
    Templatefy = require("../lib/templatefy"),
    cli = path.resolve('./bin/templatefy'),
    pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8'),
    instanbul_cli = path.join(require.resolve('istanbul'), '..', require('istanbul/package.json').bin.istanbul),
    mocha = require.resolve('mocha'),
    templatefy = function() {
        var mproc, sproc,
            args = Array.prototype.slice.call(arguments),
            cb = args.splice(-1)[0],
            targs = args.splice(-1)[0] || '',
            pcmd = args.splice(-1)[0],
            stdout = '',
            stderr = '',
            err = null,
            cargs = [
              'cover',
              '--handle-sigint',
              '--root', process.cwd(),
              '--dir', path.join(process.cwd(), './coverage'),
              '--report=none',
              '--print=none',
              '--include-pid',
              cli, '--'
            ].concat(targs);
        mproc = cproc.fork(instanbul_cli, cargs, {cwd: process.cwd(), env: process.env, silent: true});

        if(pcmd)
            cproc.exec(pcmd).stdout.pipe(mproc.stdin);

        mproc.on('error', function (e) {
            err = new Error(e);
        });

        mproc.stdout.on('data', function (data) {
            stdout += data.toString();
        });

        mproc.stderr.on('data', function (data) {
            stderr += data.toString();
        });

        mproc.on('close', function (code) {
            cb(err, stdout, stderr);
        });

        return mproc;
    };

describe('Templatefy', function() {

    describe('#command-line', function() {
        this.timeout(15000);
        it('should print help to stdout', function(done) {
            templatefy('--help', function(error, stdout, stderr) {
                expect(stdout).to.have.string('Usage:');
                expect(stdout).to.have.string('-i, --input');
                expect(stdout).to.have.string('-o, --output');
                expect(stdout).to.have.string('-s, --scope');
                expect(stdout).to.have.string('-e, --exports');
                expect(stdout).to.have.string('-r, --var');
                expect(stdout).to.have.string('-g, --global');
                expect(stdout).to.have.string('-a, --angular');
                expect(stdout).to.have.string('-l, --linter');
                expect(stdout).to.have.string('-V, --log');
                expect(stdout).to.have.string('-v, --version ');
                done();
            });
        });

        it('should print version to stdout', function(done) {
            templatefy('--version', function(error, stdout, stderr) {
                expect(stdout).to.have.string(pkg.version);
                done();
            });
        });

//        it('should require --input argument if not pipe stdin defined', function(done) {
//            templatefy('--ouput=stdout', function(error, stdout, stderr) {
//                expect(stdout).to.have.string('input argument is required');
//                done();
//            });
//        });

        it('should show error unexpected paramenter message', function(done) {
            templatefy('--wrong-param', function(error, stdout, stderr) {
                expect(stdout).to.have.string('\'--wrong-param\' expects a value');
                done();
            });
        });

        it('should get the string template by stdin then and print parsed over stdout', function(done) {
            templatefy('--input=\'
Test
\'', function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by input path parameter and print parsed over stdout', function(done) {
            templatefy('--input=./test/fixtures/template-element.html', function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by stdin pipe and print parsed over stdout', function(done) {
            templatefy('echo \'
Test
\'', null, function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by stdin then and save parsed into a file', function(done) {
            templatefy('echo \'
Test
\'', '--output=template.output.html', function(error, stdout, stderr) {
                expect(fs.readFileSync('template.output.html', 'utf-8')).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper and defined custom module local variable name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module-var=var_test'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('var var_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper and defined custom module name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module-name=module_test'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('module_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with only with $templateCache injection', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module=false'], function(error, stdout, stderr) {
                expect(stdout).to.not.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with only with $templateCache injection and custom template name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module=false', '--angular-template-name=template_test'], function(error, stdout, stderr) {
                expect(stdout).to.not.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('template_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });
    });

    describe('#api', function() {

        it('should throw an Exception (Input stream error)', function() {
             expect(Templatefy.parse).to.throw('Input stream error');
        });
    });
});
<commit_msg>add: templatefy option unit test<commit_after>const
    path = require('path'),
    fs = require('fs'),
    rewire = require("rewire"),
    expect = require("chai").expect,
    execFile = require('child_process').execFile,
    cproc = require('child_process'),
    Templatefy = require("../lib/templatefy"),
    cli = path.resolve('./bin/templatefy'),
    pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8'),
    instanbul_cli = path.join(require.resolve('istanbul'), '..', require('istanbul/package.json').bin.istanbul),
    mocha = require.resolve('mocha'),
    templatefy = function() {
        var mproc, sproc,
            args = Array.prototype.slice.call(arguments),
            cb = args.splice(-1)[0],
            targs = args.splice(-1)[0] || '',
            pcmd = args.splice(-1)[0],
            stdout = '',
            stderr = '',
            err = null,
            cargs = [
              'cover',
              '--handle-sigint',
              '--root', process.cwd(),
              '--dir', path.join(process.cwd(), './coverage'),
              '--report=none',
              '--print=none',
              '--include-pid',
              cli, '--'
            ].concat(targs);
        mproc = cproc.fork(instanbul_cli, cargs, {cwd: process.cwd(), env: process.env, silent: true});

        if(pcmd)
            cproc.exec(pcmd).stdout.pipe(mproc.stdin);

        mproc.on('error', function (e) {
            err = new Error(e);
        });

        mproc.stdout.on('data', function (data) {
            stdout += data.toString();
        });

        mproc.stderr.on('data', function (data) {
            stderr += data.toString();
        });

        mproc.on('close', function (code) {
            cb(err, stdout, stderr);
        });

        return mproc;
    };

describe('Templatefy', function() {

    describe('#command-line', function() {
        this.timeout(15000);
        it('should print help to stdout', function(done) {
            templatefy('--help', function(error, stdout, stderr) {
                expect(stdout).to.have.string('Usage:');
                expect(stdout).to.have.string('-i, --input');
                expect(stdout).to.have.string('-o, --output');
                expect(stdout).to.have.string('-s, --scope');
                expect(stdout).to.have.string('-e, --exports');
                expect(stdout).to.have.string('-r, --var');
                expect(stdout).to.have.string('-g, --global');
                expect(stdout).to.have.string('-a, --angular');
                expect(stdout).to.have.string('-l, --linter');
                expect(stdout).to.have.string('-V, --log');
                expect(stdout).to.have.string('-v, --version ');
                done();
            });
        });

        it('should print version to stdout', function(done) {
            templatefy('--version', function(error, stdout, stderr) {
                expect(stdout).to.have.string(pkg.version);
                done();
            });
        });
//        it('should require --input argument if not pipe stdin defined', function(done) {
//            templatefy('--ouput=stdout', function(error, stdout, stderr) {
//                expect(stdout).to.have.string('input argument is required');
//                done();
//            });
//        });
        it('should show error unexpected paramenter message', function(done) {
            templatefy('--wrong-param', function(error, stdout, stderr) {
                expect(stdout).to.have.string('\'--wrong-param\' expects a value');
                done();
            });
        });

        it('should get the string template by stdin then and print parsed over stdout', function(done) {
            templatefy('--input=\'
Test
\'', function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by input path parameter and print parsed over stdout', function(done) {
            templatefy('--input=./test/fixtures/template-element.html', function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by stdin pipe and print parsed over stdout', function(done) {
            templatefy('echo \'
Test
\'', null, function(error, stdout, stderr) {
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should get the string template by stdin then and save parsed into a file', function(done) {
            templatefy('echo \'
Test
\'', '--output=template.output.html', function(error, stdout, stderr) {
                expect(fs.readFileSync('template.output.html', 'utf-8')).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper and defined custom module local variable name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module-var=var_test'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('var var_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with angular module wrapper and defined custom module name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module-name=module_test'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('module_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with only with $templateCache injection', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module=false'], function(error, stdout, stderr) {
                expect(stdout).to.not.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with only with $templateCache injection and custom template name', function(done) {
            templatefy(['--input=\'
Test
\'', '--angular', '--angular-module=false', '--angular-template-name=template_test'], function(error, stdout, stderr) {
                expect(stdout).to.not.have.string('angular.module');
                expect(stdout).to.have.string('$templateCache');
                expect(stdout).to.have.string('template_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template scoped in a function clousure', function(done) {
            templatefy(['--input=\'
Test
\'', '--scope'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('!(function(global, module){');
                expect(stdout).to.have.string('typeof global !== "undefined"');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with variable assignment', function(done) {
            templatefy(['--input=\'
Test
\'', '--var=var_test'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('var var_test');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with global property assignment', function(done) {
            templatefy(['--input=\'
Test
\'', '--global=globalTest:globalProperty'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('globalTest[\'globalProperty\']');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with global property assignment in window', function(done) {
            templatefy(['--input=\'
Test
\'', '--global=globalProperty'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('window');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template with global property assignment in global', function(done) {
            templatefy(['--input=\'
Test
\'', '--global=globalProperty --scope'], function(error, stdout, stderr) {
                expect(stdout).to.have.string('global');
                expect(stdout).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template and exports as module', function(done) {
            templatefy(['--input=\'
Test
\'' , '--output=unit-test-module.js', '--exports'], function(error, stdout, stderr) {
                var template = require(path.join(process.cwd(), './unit-test-module.js'));
                expect(template).to.have.string('
Test
');
                done();
            });
        });

        it('should parsed template and exports as global property in a scoped clousure', function(done) {
            templatefy(['--input=\'
Test
\'' , '--output=unit-test-module.js', '--scope', '--global=templatefy'], function(error, stdout, stderr) {
                expect(global.templatefy).to.not.be.ok;
                rewire(path.join(process.cwd(), './unit-test-module.js'));
                expect(global.templatefy).to.be.ok;
                expect(global.templatefy).to.have.string('
Test
');
                done();
            });
        });
    });

    describe('#api', function() {

        it('should throw an Exception (Input stream error)', function() {
             expect(Templatefy.parse).to.throw('Input stream error');
        });
    });
});
<|endoftext|>


<commit_before>export default class ExpensesService {
  constructor($http, UserService, $stateParams) {
    this.$inject = ['$http', 'UserService', '$stateParams'];
    this.http = $http;
    this.stateParams = $stateParams;
    this.UserService = UserService;

    // Expenses Overview
    this.expenses = [];

    // Summary Bar
    this.summary = {};

    // Transaction Page
    this.transactions = [];
    this.filterBy = 'All';

    // Expenses Summary
    this.consolidatedTransactions = '';
    this.consolidatedSummary = '';

    // Bindings
    this.calculateDebts = this.calculateDebts.bind(this);
    this.getExpenses = this.getExpenses.bind(this);
    this.roundMoney = this.roundMoney.bind(this);
    this.filterTransactions = this.filterTransactions.bind(this);
    this.removeExpense = this.removeExpense.bind(this);
    this.removeTransaction = this.removeTransaction.bind(this);
    this.settleTransaction = this.settleTransaction.bind(this);
    this.changeFilter = this.changeFilter.bind(this);
    this.filterByOwed = this.filterByOwed.bind(this);
    this.filterByOwedTo = this.filterByOwedTo.bind(this);
    this.filterByExpenseId = this.filterByExpenseId.bind(this);
    this.consolidateDebts = this.consolidateDebts.bind(this);
    this.consolidateSummary = this.consolidateSummary.bind(this);
  }

  // Data Manipulation Methods

  filterTransactions() {
    let result = [];
    this.expenses.forEach((expense) => {
      expense.transactions.forEach((transaction) => {
        transaction.description = expense.description;
        result.push(transaction);
      });
    });
    return result;
  }

  calculateDebts() {
    let owed = 0;
    let debt = 0;
    this.expenses.forEach((expense) => {
      expense.transactions.forEach((transaction) => {
        if (transaction.status === 'open') {
          if (transaction.to._id === this.UserService.user.id) {
            owed += transaction.amount;
          } else if (transaction.from._id === this.UserService.user.id) {
            debt += transaction.amount;
          }
        }
      });
    });
    return {owed: this.roundMoney(owed), debt: this.roundMoney(debt), balance: this.roundMoney(owed - debt)};
  }

  consolidateDebts() {
    let owes = {};
    let isOwed = {};
    let consolidatedTransactions = [];

    // Iterate through all transactions and sum up what all people owe and are owed
    console.log('transactions', this.transactions);
    // Fill owes
    this.transactions.forEach((transaction) => {
      if (!owes[transaction.from._id] && owes[transaction.from._id] !== 0) {
        owes[transaction.from._id] = {from: transaction.from, amount: transaction.amount};
      } else {
        owes[transaction.from._id].amount += transaction.amount;
      }
      // Fill isOwed
      if (!isOwed[transaction.to._id] && owes[transaction.to._id] !== 0) {
        isOwed[transaction.to._id] = {to: transaction.to, amount: transaction.amount};
      } else {
        isOwed[transaction.to._id].amount += transaction.amount;
      }
    });

    // Iterate through owes Array, find corresponding key if possible in isOwed Array and cancel out transactions with same ID
    for (let id in owes) {
      if (isOwed[id] || isOwed[id] === 0) {
        // Check if one is bigger
        if (owes[id].amount > isOwed[id].amount) {
          owes[id].amount -= isOwed[id].amount;
          delete isOwed[id];
        } else if (owes[id].amount < isOwed[id].amount) {
          isOwed[id].amount -= owes[id].amount;
          delete owes[id];
        } else if (owes[id].amount === isOwed[id].amount) {
          delete owes[id];
          delete isOwed[id];
        }
      }
    }

    // TODO: May be useful to filter and pre make equal transactions first to simplify transactions

    // Iterate through both arrays and create transactions, simplifying and deleting as needed
    for (let fromId in owes) {
      while (owes[fromId]) {
        for (let toId in isOwed) {
          let transaction = {from: owes[fromId].from, to: isOwed[toId].to};
          if (owes[fromId].amount < isOwed[toId].amount) {
            transaction.amount = owes[fromId].amount;
            isOwed[toId].amount -= transaction.amount;
            delete owes[fromId];
          } else if (owes[fromId].amount > isOwed[toId].amount) {
            transaction.amount = isOwed[toId].amount;
            owes[fromId].amount -= transaction.amount;
            delete isOwed[toId];
          } else if (owes[fromId].amount === isOwed[toId].amount) {
            transaction.amount = isOwed[toId].amount;
            delete owes[fromId];
            delete isOwed[toId];
          }
          consolidatedTransactions.push(transaction);
        }
      }
    }
    console.log('owes', owes, 'consTrans', consolidatedTransactions);
    return consolidatedTransactions;
  }

  consolidateSummary() {
    let owe = 0;
    let receive = 0;
    this.consolidatedTransactions.forEach((transaction) => {
      if (transaction.from._id === this.UserService.user.id) {
        owe += transaction.amount;
      }
      if (transaction.to._id === this.UserService.user.id) {
        receive += transaction.amount;
      }
    });
    return {owe: owe, receive: receive};
  }

  // Angular filtering Methods

  changeFilter(type) {
    this.filterBy = type;
  }

  filterByOwed(transaction) {
    if (transaction.to._id === this.UserService.user.id) {
      return true;
    } else {
      return false;
    }
  }

  filterByOwedTo(transaction) {
    if (transaction.from._id === this.UserService.user.id) {
      return true;
    } else {
      return false;
    }
  }

  filterByExpenseId(transaction) {
    if (transaction.expenseId === this.stateParams.expenseId) {
      return true;
    } else {
      return false;
    }
  }

  // API Methods

  newExpense(expense) {
    return this.http.post('/api/expenses', expense).then(() => {
      this.getExpenses();
    });
  }

  getExpenses() {
    return this.http.get(`/api/expenses/${this.stateParams.groupId}`).then((res) => {
      this.expenses = res.data;
      this.summary = this.calculateDebts();
      this.transactions = this.filterTransactions();
      this.consolidatedTransactions = this.consolidateDebts();
      this.consolidatedSummary = this.consolidateSummary();
      console.log('consolidated transactions: ', this.consolidatedTransactions);
    });
  }

  addTransaction(expenseId, transaction) {
    return this.http.post(`/api/expenses/${expenseId}/add`, transaction).then(() => this.getExpenses());
  }

  removeTransaction(transactionId) {
    return this.http.delete(`/api/expenses/transaction/${transactionId}/remove`).then(() => this.getExpenses());
  }

  settleTransaction(transactionId) {
    return this.http.put(`/api/expenses/transaction/${transactionId}/settle`).then(() => this.getExpenses());
  }

  removeExpense(expenseId) {
    return this.http.delete(`/api/expenses/${expenseId}/remove`).then(() => this.getExpenses());
  }

  roundMoney(value) {
    return Number(Math.round(value + 'e+2') + 'e-2').toFixed(2);
  }

}
<commit_msg>Removed extraneous if statement and console logs<commit_after>export default class ExpensesService {
  constructor($http, UserService, $stateParams) {
    this.$inject = ['$http', 'UserService', '$stateParams'];
    this.http = $http;
    this.stateParams = $stateParams;
    this.UserService = UserService;

    // Expenses Overview
    this.expenses = [];

    // Summary Bar
    this.summary = {};

    // Transaction Page
    this.transactions = [];
    this.filterBy = 'All';

    // Expenses Summary
    this.consolidatedTransactions = '';
    this.consolidatedSummary = '';

    // Bindings
    this.calculateDebts = this.calculateDebts.bind(this);
    this.getExpenses = this.getExpenses.bind(this);
    this.roundMoney = this.roundMoney.bind(this);
    this.filterTransactions = this.filterTransactions.bind(this);
    this.removeExpense = this.removeExpense.bind(this);
    this.removeTransaction = this.removeTransaction.bind(this);
    this.settleTransaction = this.settleTransaction.bind(this);
    this.changeFilter = this.changeFilter.bind(this);
    this.filterByOwed = this.filterByOwed.bind(this);
    this.filterByOwedTo = this.filterByOwedTo.bind(this);
    this.filterByExpenseId = this.filterByExpenseId.bind(this);
    this.consolidateDebts = this.consolidateDebts.bind(this);
    this.consolidateSummary = this.consolidateSummary.bind(this);
  }

  // Data Manipulation Methods

  filterTransactions() {
    let result = [];
    this.expenses.forEach((expense) => {
      expense.transactions.forEach((transaction) => {
        transaction.description = expense.description;
        result.push(transaction);
      });
    });
    return result;
  }

  calculateDebts() {
    let owed = 0;
    let debt = 0;
    this.expenses.forEach((expense) => {
      expense.transactions.forEach((transaction) => {
        if (transaction.status === 'open') {
          if (transaction.to._id === this.UserService.user.id) {
            owed += transaction.amount;
          } else if (transaction.from._id === this.UserService.user.id) {
            debt += transaction.amount;
          }
        }
      });
    });
    return {owed: this.roundMoney(owed), debt: this.roundMoney(debt), balance: this.roundMoney(owed - debt)};
  }

  consolidateDebts() {
    let owes = {};
    let isOwed = {};
    let consolidatedTransactions = [];

    // Iterate through all transactions and sum up what all people owe and are owed

    // Fill owes
    this.transactions.forEach((transaction) => {
      if (!owes[transaction.from._id]) {
        owes[transaction.from._id] = {from: transaction.from, amount: transaction.amount};
      } else {
        owes[transaction.from._id].amount += transaction.amount;
      }
      // Fill isOwed
      if (!isOwed[transaction.to._id]) {
        isOwed[transaction.to._id] = {to: transaction.to, amount: transaction.amount};
      } else {
        isOwed[transaction.to._id].amount += transaction.amount;
      }
    });

    // Iterate through owes Array, find corresponding key if possible in isOwed Array and cancel out transactions with same ID
    for (let id in owes) {
      if (isOwed[id] || isOwed[id] === 0) {
        // Check if one is bigger
        if (owes[id].amount > isOwed[id].amount) {
          owes[id].amount -= isOwed[id].amount;
          delete isOwed[id];
        } else if (owes[id].amount < isOwed[id].amount) {
          isOwed[id].amount -= owes[id].amount;
          delete owes[id];
        } else if (owes[id].amount === isOwed[id].amount) {
          delete owes[id];
          delete isOwed[id];
        }
      }
    }

    // TODO: May be useful to filter and pre make equal transactions first to simplify transactions

    // Iterate through both arrays and create transactions, simplifying and deleting as needed
    for (let fromId in owes) {
      while (owes[fromId]) {
        for (let toId in isOwed) {
          let transaction = {from: owes[fromId].from, to: isOwed[toId].to};
          if (owes[fromId].amount < isOwed[toId].amount) {
            transaction.amount = owes[fromId].amount;
            isOwed[toId].amount -= transaction.amount;
            delete owes[fromId];
          } else if (owes[fromId].amount > isOwed[toId].amount) {
            transaction.amount = isOwed[toId].amount;
            owes[fromId].amount -= transaction.amount;
            delete isOwed[toId];
          } else if (owes[fromId].amount === isOwed[toId].amount) {
            transaction.amount = isOwed[toId].amount;
            delete owes[fromId];
            delete isOwed[toId];
          }
          consolidatedTransactions.push(transaction);
        }
      }
    }
    return consolidatedTransactions;
  }

  consolidateSummary() {
    let owe = 0;
    let receive = 0;
    this.consolidatedTransactions.forEach((transaction) => {
      if (transaction.from._id === this.UserService.user.id) {
        owe += transaction.amount;
      }
      if (transaction.to._id === this.UserService.user.id) {
        receive += transaction.amount;
      }
    });
    return {owe: owe, receive: receive};
  }

  // Angular filtering Methods

  changeFilter(type) {
    this.filterBy = type;
  }

  filterByOwed(transaction) {
    if (transaction.to._id === this.UserService.user.id) {
      return true;
    } else {
      return false;
    }
  }

  filterByOwedTo(transaction) {
    if (transaction.from._id === this.UserService.user.id) {
      return true;
    } else {
      return false;
    }
  }

  filterByExpenseId(transaction) {
    if (transaction.expenseId === this.stateParams.expenseId) {
      return true;
    } else {
      return false;
    }
  }

  // API Methods

  newExpense(expense) {
    return this.http.post('/api/expenses', expense).then(() => {
      this.getExpenses();
    });
  }

  getExpenses() {
    return this.http.get(`/api/expenses/${this.stateParams.groupId}`).then((res) => {
      this.expenses = res.data;
      this.summary = this.calculateDebts();
      this.transactions = this.filterTransactions();
      this.consolidatedTransactions = this.consolidateDebts();
      this.consolidatedSummary = this.consolidateSummary();
    });
  }

  addTransaction(expenseId, transaction) {
    return this.http.post(`/api/expenses/${expenseId}/add`, transaction).then(() => this.getExpenses());
  }

  removeTransaction(transactionId) {
    return this.http.delete(`/api/expenses/transaction/${transactionId}/remove`).then(() => this.getExpenses());
  }

  settleTransaction(transactionId) {
    return this.http.put(`/api/expenses/transaction/${transactionId}/settle`).then(() => this.getExpenses());
  }

  removeExpense(expenseId) {
    return this.http.delete(`/api/expenses/${expenseId}/remove`).then(() => this.getExpenses());
  }

  roundMoney(value) {
    return Number(Math.round(value + 'e+2') + 'e-2').toFixed(2);
  }

}
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   Glossa - mainPage
// Copyright: ©2010 The Text Laboratory, University of Oslo
// ==========================================================================

sc_require('views/query_row');

/*globals Glossa */

// This page describes the main user interface for your application.
Glossa.mainPage = SC.Page.design({

  // The main pane is made visible on screen as soon as your app is loaded.
  // Add childViews to this pane for views to display immediately on page
  // load.
  mainPane: SC.MainPane.design({
    childViews: 'tabView'.w(),

    tabView: SC.TabView.design({
      items: [
        { title: 'Search', value: 'Glossa.mainPage.mainPane.tabView.search' },
        { title: 'Results', value: 'Glossa.mainPage.mainPane.tabView.results' }
      ],
      itemTitleKey: 'title',
      itemValueKey: 'value',

      // The upper part of the search interface
      search: SC.View.design({
        childViews: 'buttons queryRows'.w(),

        buttons: SC.View.design({
          layout: { left: 10, top: 10, width: 300, height: 45 },
          childViews: 'resetButton searchButton'.w(),

          resetButton: SC.ButtonView.design({
            layout: { width: 100 },
            title: 'Reset form',
            action: 'myMethod',
            target: 'MyApp.Controller'
          }),

          searchButton: SC.ButtonView.design({
            layout: { left: 110, width: 100 },
            title: 'Search',
            action: 'search',
            target: 'Glossa.searchController',
            isDefault: YES
          })
        }),

        queryRows: SC.ScrollView.design({
          layout: { top: 45, right: 0, bottom: 0, left: 0 },

          contentView: SC.CollectionView.design({
            contentBinding: 'Glossa.searchController.queries',

            // Each QueryRowView contains a horizontal sequence of query terms
            exampleView: Glossa.QueryRowView
          })
        })

      }),

      results: SC.ScrollView.design({
        contentView: SC.ListView.design({
          layout: { top: 10, bottom: 10 },
          contentBinding: 'Glossa.searchResultsArrayController.arrangedObjects',
          contentValueKey: 'line',
          showAlternatingRows: YES,
          exampleView: SC.ListItemView.design({
            escapeHTML: NO
          })
        })
      })

    })

  })

});
<commit_msg>Show search view at startup<commit_after>// ==========================================================================
// Project:   Glossa - mainPage
// Copyright: ©2010 The Text Laboratory, University of Oslo
// ==========================================================================

sc_require('views/query_row');

/*globals Glossa */

// This page describes the main user interface for your application.
Glossa.mainPage = SC.Page.design({

  // The main pane is made visible on screen as soon as your app is loaded.
  // Add childViews to this pane for views to display immediately on page
  // load.
  mainPane: SC.MainPane.design({
    childViews: 'tabView'.w(),

    tabView: SC.TabView.design({
      items: [
        { title: 'Criteria', value: 'Glossa.searchView' },
        { title: 'Results', value: 'Glossa.resultsView' }
      ],
      itemTitleKey: 'title',
      itemValueKey: 'value',
      nowShowing: 'Glossa.searchView',

      init: function() {
        sc_super();
      }

    })

  })

});

// The content views of Glossa.mainPage.mainPane.tabView. These cannot be defined
// within Glossa.mainPage because it leads to infinite recursion if nowShowing
// is set (since Glossa.mainPage and all its child panels will be created then, including
// the TabView, which leads to setting of nowShowing, which leads to creation of Glossa.mainPage
// etc. ad infinitum...).
Glossa.searchView = SC.View.design({
  childViews: 'buttons queryRows'.w(),

  buttons: SC.View.design({
    layout: { left: 10, top: 10, width: 300, height: 45 },
    childViews: 'resetButton searchButton'.w(),

    resetButton: SC.ButtonView.design({
      layout: { width: 100 },
      title: 'Reset form',
      action: 'myMethod',
      target: 'MyApp.Controller'
    }),

    searchButton: SC.ButtonView.design({
      layout: { left: 110, width: 100 },
      title: 'Search',
      action: 'search',
      target: 'Glossa.searchController',
      isDefault: YES
    })
  }),

  queryRows: SC.ScrollView.design({
    layout: { top: 45, right: 0, bottom: 0, left: 0 },

    contentView: SC.CollectionView.design({
      contentBinding: 'Glossa.searchController.queries',

      // Each QueryRowView contains a horizontal sequence of query terms
      exampleView: Glossa.QueryRowView
    })
  })

});

Glossa.resultsView = SC.ScrollView.design({
  contentView: SC.ListView.design({
    layout: { top: 10, bottom: 10 },
    contentBinding: 'Glossa.searchResultsArrayController.arrangedObjects',
    contentValueKey: 'line',
    showAlternatingRows: YES,
    exampleView: SC.ListItemView.design({
      escapeHTML: NO
    })
  })
});

<|endoftext|>