<commit_before>// app/components/EditSetHeader.js

// NOTE: this is one of the places where I'm using internal state rather than redux global state
// reason for doing so is because this intended to be used multiple times in a giant listview
// it's therefore easier to keep the state attached to the component rather than build a giant array in redux

import React, {Component} from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TouchableHighlight, TextInput, Keyboard, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Pill from './Pill';

class EditSetHeader extends Component {

    constructor(props) {
		super(props);

		this.state = {
            setNumber: this.props.setNumber,
            exercise: this.props.exercise,
            tags: this.props.tags,
            weight: this.props.weight,
            metric: this.props.metric,
            rpe: this.props.rpe,
            removed: this.props.removed,
            editingExercise: false,
            suggestions: []
        };
	}

    componentWillReceiveProps(nextProps) {
        this.setState({
            setNumber: nextProps.setNumber,
            exercise: nextProps.exercise,
            tags: nextProps.tags,
            weight: nextProps.weight,
            metric: nextProps.metric,
            rpe: nextProps.rpe,
            removed: nextProps.removed
        });
	}

    // KEYBOARD

    componentWillMount() {
        this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', () => { this._keyboardWillShow() });
        this.keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => { this._keyboardWillHide() });
        this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { this._keyboardDidHide() });
    }

    componentWillUnmount() {
        this.keyboardWillShowListener.remove();
        this.keyboardWillHideListener.remove();
        this.keyboardDidHideListener.remove();
    }

    _keyboardWillShow() {
        // TODO: scroll to the appropriate position - ios only
    }

    _keyboardWillHide() {
        // TODO: scroll to the appropriate position - ios only
    }

    _keyboardDidHide() {
        this._save();
    }

    // SAVING

    componentDidUpdate(nextProps, nextState) {
        if (nextState.metric !== this.state.metric) {
            this._save();
        }
    }

    _save() {
        if (this.props.exercise !== this.state.exercise
            || this.props.weight !== this.state.weight
            || this.props.metric !== this.state.metric
            || this.props.rpe !== this.state.rpe) {
            this.props.updateSet(this.props.setID, this.state.exercise, this.state.weight, this.state.metric, this.state.rpe);
        }
    }

    _toggleMetric() {
        if (this.state.metric === 'kgs') {
            this.setState({metric: 'lbs'})
        } else if (this.state.metric === 'lbs') {
            this.setState({metric: 'kgs'})
        }
    }

    // RENDER

    _displayExercise() {
        if (this.state.exercise === null || this.state.exercise === '') {
            return (<Text style={[styles.exerciseText, styles.placeholderText]}>Enter Exercise</Text>);
        }
        return (<Text style={styles.exerciseText}>{this.state.exercise}</Text>);
    }

    _displayTags() {
        if (this.state.tags === undefined || this.state.tags === null || this.state.tags.length === 0) {
            return (<Text style={[styles.exerciseText, styles.placeholderText]}>Enter Tags</Text>);
        }

        var pills = [];
        let position = 0;
        this.state.tags.map((tag) => {
            let key = position;
            pills.push(
                <Pill key={key} text={tag} style={{paddingRight: 5, paddingBottom: 3}} />
            );
            position++;
        });

        return (<View style={styles.tagField}>{pills}</View>);
    }

    _displaySetNumber() {
        if (this.state.removed) {
            return null;
        }
        if (this.state.setNumber === null || this.state.setNumber === undefined) {
            return '#1';
        }
        return '#' + this.state.setNumber;
    }

    _displayMetric() {
        if (this.state.metric === 'kgs') {
            return "KGs";
        } else if (this.state.metric === 'lbs') {
            return "LBs";
        }
    }

    render() {
        return (
            <View style={{flex: 1, flexDirection: 'column', opacity: this.state.removed ? 0.3 : 1}}>
                <View style={styles.upperShadow} />
                <View style={[styles.shadow, {flex: 1, flexDirection: 'column', padding: 5}]}>
                    <View style={styles.field}>
                        <TouchableHighlight onPress={() => this.props.editExercise(this.props.setID, this.state.exercise)}>
                            {this._displayExercise()}
                        </TouchableHighlight>
                        <View style={styles.fieldDetails} pointerEvents='none'>
                            <Text style={styles.detailText}>{this._displaySetNumber()}</Text>
                        </View>
                    </View>
                    <View style={{flex: 1, flexDirection: 'row'}}>
                        <View style={[styles.field, {flex: 1, marginRight: 5}]}>
                            <TextInput
                                style={styles.fieldText}
                                keyboardType={'numeric'}
                                underlineColorAndroid={'transparent'}
                                editable = {true}
                                placeholder="Enter Weight"
                                placeholderTextColor={'lightgray'}
                                value={this.state.weight}
                                onChangeText={(weight) => this.setState({weight: weight}) }
                            />
                            <View style={styles.fieldDetails}>
                                <TouchableOpacity onPress={() => this._toggleMetric()}>
                                    <View style={{flexDirection: 'row', alignItems: 'center', }}>
                                        <Text style={styles.detailText}>{this._displayMetric()} </Text>
                                        <Icon name="refresh" size={10} color="gray" />
                                    </View>
                                </TouchableOpacity>
                            </View>
                        </View>

                        <View style={[styles.field, {flex: 1}]}>
                            <TextInput
                                style={styles.fieldText}
                                keyboardType={'numeric'}
                                underlineColorAndroid={'transparent'}
                                editable = {true}
                                placeholder="Enter RPE"
                                placeholderTextColor={'lightgray'}
                                value = {this.state.rpe}
                                onChangeText={(rpe) => this.setState({rpe: rpe}) }
                            />
                            <View style={styles.fieldDetails} pointerEvents='none'>
                                <Text style={styles.detailText}>RPE</Text>
                            </View>
                        </View>
                    </View>

                    <View>
                        <View style={[styles.field, {flex: 1}]}>
                            <TouchableHighlight onPress={() => this.props.editTags(this.props.setID, this.state.tags)}>
                                {this._displayTags()}
                            </TouchableHighlight>
                        </View>
                    </View>
                </View>
            </View>
        );
    }

}

const styles = StyleSheet.create({
    field: {
        backgroundColor: 'rgba(239, 239, 239, 1)',
        borderColor: 'rgba(239, 239, 239, 1)',
        borderWidth: 3,
        borderRadius: 3,
        marginBottom: 5,
        zIndex: 2,
        minHeight: (Platform.OS === 'ios') ? 30 : 40,
    },
    fieldDetails: {
        position: 'absolute',
        right: 5,
        top: 0,
        bottom: 0,
        justifyContent: 'center',
    },
    fieldText: {
        height: (Platform.OS === 'ios') ? 30 : 40,
        fontSize: 15,
        paddingRight: 30
    },
    exerciseText: {
        height: (Platform.OS === 'ios') ? 30 : 40,
        marginTop: (Platform.OS === 'ios') ? 5 : 8,
        marginLeft: (Platform.OS === 'ios') ? 0 : 4,
        fontSize: 15,
        paddingRight: 30,
        color: 'black'
    },
    tagField: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        minHeight: (Platform.OS === 'ios') ? 30 : 40,
        paddingLeft: 2,
        paddingRight: 0,
    },
    placeholderText: {
        color: 'lightgray'
    },
    detailText: {
        color: 'gray'
    },
    upperShadow: {
		shadowColor: "#000000",
        shadowRadius: 2,
        shadowOpacity: 1,
        shadowOffset: {
            height: 1,
            weight: 0
        },
        height: 1,
        backgroundColor: 'white'
    },
	shadow: {
		shadowColor: "#000000",
		shadowOpacity: 0.2,
		shadowRadius: 2,
		shadowOffset: {
			height: 4,
			width: 0
		},
	}
});

export default EditSetHeader;
<commit_msg>tweaked css for ios<commit_after>// app/components/EditSetHeader.js

// NOTE: this is one of the places where I'm using internal state rather than redux global state
// reason for doing so is because this intended to be used multiple times in a giant listview
// it's therefore easier to keep the state attached to the component rather than build a giant array in redux

import React, {Component} from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TouchableHighlight, TextInput, Keyboard, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Pill from './Pill';

class EditSetHeader extends Component {

    constructor(props) {
		super(props);

		this.state = {
            setNumber: this.props.setNumber,
            exercise: this.props.exercise,
            tags: this.props.tags,
            weight: this.props.weight,
            metric: this.props.metric,
            rpe: this.props.rpe,
            removed: this.props.removed,
            editingExercise: false,
            suggestions: []
        };
	}

    componentWillReceiveProps(nextProps) {
        this.setState({
            setNumber: nextProps.setNumber,
            exercise: nextProps.exercise,
            tags: nextProps.tags,
            weight: nextProps.weight,
            metric: nextProps.metric,
            rpe: nextProps.rpe,
            removed: nextProps.removed
        });
	}

    // KEYBOARD

    componentWillMount() {
        this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', () => { this._keyboardWillShow() });
        this.keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => { this._keyboardWillHide() });
        this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { this._keyboardDidHide() });
    }

    componentWillUnmount() {
        this.keyboardWillShowListener.remove();
        this.keyboardWillHideListener.remove();
        this.keyboardDidHideListener.remove();
    }

    _keyboardWillShow() {
        // TODO: scroll to the appropriate position - ios only
    }

    _keyboardWillHide() {
        // TODO: scroll to the appropriate position - ios only
    }

    _keyboardDidHide() {
        this._save();
    }

    // SAVING

    componentDidUpdate(nextProps, nextState) {
        if (nextState.metric !== this.state.metric) {
            this._save();
        }
    }

    _save() {
        if (this.props.exercise !== this.state.exercise
            || this.props.weight !== this.state.weight
            || this.props.metric !== this.state.metric
            || this.props.rpe !== this.state.rpe) {
            this.props.updateSet(this.props.setID, this.state.exercise, this.state.weight, this.state.metric, this.state.rpe);
        }
    }

    _toggleMetric() {
        if (this.state.metric === 'kgs') {
            this.setState({metric: 'lbs'})
        } else if (this.state.metric === 'lbs') {
            this.setState({metric: 'kgs'})
        }
    }

    // RENDER

    _displayExercise() {
        if (this.state.exercise === null || this.state.exercise === '') {
            return (<Text style={[styles.exerciseText, styles.placeholderText]}>Enter Exercise</Text>);
        }
        return (<Text style={styles.exerciseText}>{this.state.exercise}</Text>);
    }

    _displayTags() {
        if (this.state.tags === undefined || this.state.tags === null || this.state.tags.length === 0) {
            return (<Text style={[styles.exerciseText, styles.placeholderText]}>Enter Tags</Text>);
        }

        var pills = [];
        let position = 0;
        this.state.tags.map((tag) => {
            let key = position;
            pills.push(
                <Pill key={key} text={tag} style={{paddingRight: 5, paddingBottom: 3}} />
            );
            position++;
        });

        return (<View style={styles.tagField}>{pills}</View>);
    }

    _displaySetNumber() {
        if (this.state.removed) {
            return null;
        }
        if (this.state.setNumber === null || this.state.setNumber === undefined) {
            return '#1';
        }
        return '#' + this.state.setNumber;
    }

    _displayMetric() {
        if (this.state.metric === 'kgs') {
            return "KGs";
        } else if (this.state.metric === 'lbs') {
            return "LBs";
        }
    }

    render() {
        return (
            <View style={{flex: 1, flexDirection: 'column', opacity: this.state.removed ? 0.3 : 1}}>
                <View style={styles.upperShadow} />
                <View style={[styles.shadow, {flex: 1, flexDirection: 'column', padding: 5}]}>
                    <View style={styles.field}>
                        <TouchableHighlight onPress={() => this.props.editExercise(this.props.setID, this.state.exercise)}>
                            {this._displayExercise()}
                        </TouchableHighlight>
                        <View style={styles.fieldDetails} pointerEvents='none'>
                            <Text style={styles.detailText}>{this._displaySetNumber()}</Text>
                        </View>
                    </View>
                    <View style={{flex: 1, flexDirection: 'row'}}>
                        <View style={[styles.field, {flex: 1, marginRight: 5}]}>
                            <TextInput
                                style={styles.fieldText}
                                keyboardType={'numeric'}
                                underlineColorAndroid={'transparent'}
                                editable = {true}
                                placeholder="Enter Weight"
                                placeholderTextColor={'lightgray'}
                                value={this.state.weight}
                                onChangeText={(weight) => this.setState({weight: weight}) }
                            />
                            <View style={styles.fieldDetails}>
                                <TouchableOpacity onPress={() => this._toggleMetric()}>
                                    <View style={{flexDirection: 'row', alignItems: 'center', }}>
                                        <Text style={styles.detailText}>{this._displayMetric()} </Text>
                                        <Icon name="refresh" size={10} color="gray" />
                                    </View>
                                </TouchableOpacity>
                            </View>
                        </View>

                        <View style={[styles.field, {flex: 1}]}>
                            <TextInput
                                style={styles.fieldText}
                                keyboardType={'numeric'}
                                underlineColorAndroid={'transparent'}
                                editable = {true}
                                placeholder="Enter RPE"
                                placeholderTextColor={'lightgray'}
                                value = {this.state.rpe}
                                onChangeText={(rpe) => this.setState({rpe: rpe}) }
                            />
                            <View style={styles.fieldDetails} pointerEvents='none'>
                                <Text style={styles.detailText}>RPE</Text>
                            </View>
                        </View>
                    </View>

                    <View>
                        <View style={[styles.field, {flex: 1}]}>
                            <TouchableHighlight onPress={() => this.props.editTags(this.props.setID, this.state.tags)}>
                                {this._displayTags()}
                            </TouchableHighlight>
                        </View>
                    </View>
                </View>
            </View>
        );
    }

}

const styles = StyleSheet.create({
    field: {
        backgroundColor: 'rgba(239, 239, 239, 1)',
        borderColor: 'rgba(239, 239, 239, 1)',
        borderWidth: 3,
        borderRadius: 3,
        marginBottom: 5,
        zIndex: 2,
        minHeight: (Platform.OS === 'ios') ? 30 : 40,
    },
    fieldDetails: {
        position: 'absolute',
        right: 5,
        top: 0,
        bottom: 0,
        justifyContent: 'center',
    },
    fieldText: {
        height: (Platform.OS === 'ios') ? 30 : 40,
        fontSize: 15,
        paddingRight: 30
    },
    exerciseText: {
        height: (Platform.OS === 'ios') ? 30 : 40,
        paddingTop: (Platform.OS === 'ios') ? 6 : 8,
        paddingLeft: (Platform.OS === 'ios') ? 0 : 4,
        fontSize: 15,
        paddingRight: 30,
        color: 'black'
    },
    tagField: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        minHeight: (Platform.OS === 'ios') ? 30 : 40,
        paddingLeft: 2,
        paddingRight: 0,
    },
    placeholderText: {
        color: 'lightgray'
    },
    detailText: {
        color: 'gray'
    },
    upperShadow: {
		shadowColor: "#000000",
        shadowRadius: 2,
        shadowOpacity: 1,
        shadowOffset: {
            height: 1,
            weight: 0
        },
        height: 1,
        backgroundColor: 'white'
    },
	shadow: {
		shadowColor: "#000000",
		shadowOpacity: 0.2,
		shadowRadius: 2,
		shadowOffset: {
			height: 4,
			width: 0
		},
	}
});

export default EditSetHeader;
<|endoftext|>


<commit_before>const assert = require('assert');
const bibtexmisc = require('../../model/styles/bibtexMisc');
const biblatexSoftware = require('../../model/styles/biblatexSoftware');

const oneAuthorSourceData = require('../mockSourceData/allFieldsOneAuthor');
const threeAuthorsSourceData = require('../mockSourceData/allFieldsThreeAuthors');

describe('BibTexMiscStyle', () => {
  describe('format', () => {
    it('Month field, one author', () => {
      let formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes("month = {11},"),true);
    });

    it('Title field one author', () => {
      let formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes("title = {Red Barchetta},"),true);
    });

    it('Author field, one author', () => {
      let formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes("author = {Lee, Geddy},"),true);
    });

    it('Note field, one author', () => {
      let formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes("note = {Version: 1.0.0, URL: http://myUnclesFarm.com}"),true);
    });
    //Three Authors
    it('Author field, three authors', () => {
      let formattedString = bibtexmisc.format(threeAuthorsSourceData);
      assert.equal(formattedString.includes("author = {Lee, Geddy and Lifeson, Alex and Peart, Neil},"),true);
    });

  });
});

describe('BibLatexSoftwareStyle', () => {
    describe('format', () => {

      it('Month field, one author', () => {
        let formattedString = biblatexSoftware.format(oneAuthorSourceData);
        assert.equal(formattedString.includes("month = {11},"),true);
      });

      it('Title field one author', () => {
        let formattedString = biblatexSoftware.format(oneAuthorSourceData);
        assert.equal(formattedString.includes("title = {Red Barchetta},"),true);
      });

      it('Author field, one author', () => {
        let formattedString = biblatexSoftware.format(oneAuthorSourceData);
        assert.equal(formattedString.includes("author = {Lee, Geddy},"),true);
      });

      it('URL field, one author', () => {
        let formattedString = biblatexSoftware.format(oneAuthorSourceData);
        assert.equal(formattedString.includes("url = {http://myUnclesFarm.com},"),true);
      });

      it('Version field, one author', () => {
        let formattedString = biblatexSoftware.format(oneAuthorSourceData);
        assert.equal(formattedString.includes("version = {1.0.0}"),true);
      });
      //Three Authors
      it('Author field, three authors', () => {
        let formattedString = biblatexSoftware.format(threeAuthorsSourceData);
        assert.equal(formattedString.includes("author = {Lee, Geddy and Lifeson, Alex and Peart, Neil},"),true);
      });

    });
});
<commit_msg>Ran ESLint on new file<commit_after>const assert = require('assert');
const bibtexmisc = require('../../model/styles/bibtexMisc');
const biblatexSoftware = require('../../model/styles/biblatexSoftware');

const oneAuthorSourceData = require('../mockSourceData/allFieldsOneAuthor');
const threeAuthorsSourceData = require('../mockSourceData/allFieldsThreeAuthors');

describe('BibTexMiscStyle', () => {
  describe('format', () => {
    it('Month field, one author', () => {
      const formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('month = {11},'), true);
    });

    it('Title field one author', () => {
      const formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('title = {Red Barchetta},'), true);
    });

    it('Author field, one author', () => {
      const formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('author = {Lee, Geddy},'), true);
    });

    it('Note field, one author', () => {
      const formattedString = bibtexmisc.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('note = {Version: 1.0.0, URL: http://myUnclesFarm.com}'), true);
    });
    // Three Authors
    it('Author field, three authors', () => {
      const formattedString = bibtexmisc.format(threeAuthorsSourceData);
      assert.equal(formattedString.includes('author = {Lee, Geddy and Lifeson, Alex and Peart, Neil},'), true);
    });
  });
});

describe('BibLatexSoftwareStyle', () => {
  describe('format', () => {
    it('Month field, one author', () => {
      const formattedString = biblatexSoftware.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('month = {11},'), true);
    });

    it('Title field one author', () => {
      const formattedString = biblatexSoftware.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('title = {Red Barchetta},'), true);
    });

    it('Author field, one author', () => {
      const formattedString = biblatexSoftware.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('author = {Lee, Geddy},'), true);
    });

    it('URL field, one author', () => {
      const formattedString = biblatexSoftware.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('url = {http://myUnclesFarm.com},'), true);
    });

    it('Version field, one author', () => {
      const formattedString = biblatexSoftware.format(oneAuthorSourceData);
      assert.equal(formattedString.includes('version = {1.0.0}'), true);
    });
      // Three Authors
    it('Author field, three authors', () => {
      const formattedString = biblatexSoftware.format(threeAuthorsSourceData);
      assert.equal(formattedString.includes('author = {Lee, Geddy and Lifeson, Alex and Peart, Neil},'), true);
    });
  });
});
<|endoftext|>


<commit_before>import Component from '@glimmer/component';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const DEFAULT_ROLE_NAME = 'Contributor';

export default class GhRoleSelectionComponent extends Component {
    @service limit;
    @service notifications;
    @service store;

    @tracked roles = [];
    @tracked limitErrorMessage = null;

    @action
    async setRole(roleName) {
        const role = this.roles.findBy('name', roleName);
        this.args.setRole(role);
        return this.validateRole();
    }

    @task
    *fetchRolesTask() {
        const roles = yield this.store.query('role', {permissions: 'assign'});
        const defaultRole = roles.findBy('name', DEFAULT_ROLE_NAME);

        this.roles = roles;

        if (!this.args.role && defaultRole) {
            this.args.setRole(defaultRole);
        }
    }

    async validateRole(role) {
        if (role.name !== 'Contributor'
            && this.limit.limiter && this.limit.limiter.isLimited('staff')) {
            try {
                await this.limit.limiter.errorIfWouldGoOverLimit('staff');

                this.limitErrorMessage = null;
            } catch (error) {
                if (error.errorType === 'HostLimitError') {
                    this.limitErrorMessage = error.message;
                } else {
                    this.notifications.showAPIError(error, {key: 'staff.limit'});
                }
            }
        } else {
            this.limitErrorMessage = null;
        }
    }
}
<commit_msg>Fixed role validation when inviting new users<commit_after>import Component from '@glimmer/component';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const DEFAULT_ROLE_NAME = 'Contributor';

export default class GhRoleSelectionComponent extends Component {
    @service limit;
    @service notifications;
    @service store;

    @tracked roles = [];
    @tracked limitErrorMessage = null;

    @action
    async setRole(roleName) {
        const role = this.roles.findBy('name', roleName);
        this.args.setRole(role);
        return this.validateRole(role);
    }

    @task
    *fetchRolesTask() {
        const roles = yield this.store.query('role', {permissions: 'assign'});
        const defaultRole = roles.findBy('name', DEFAULT_ROLE_NAME);

        this.roles = roles;

        if (!this.args.role && defaultRole) {
            this.args.setRole(defaultRole);
        }
    }

    async validateRole(role) {
        if (role.name !== 'Contributor'
            && this.limit.limiter && this.limit.limiter.isLimited('staff')) {
            try {
                await this.limit.limiter.errorIfWouldGoOverLimit('staff');

                this.limitErrorMessage = null;
            } catch (error) {
                if (error.errorType === 'HostLimitError') {
                    this.limitErrorMessage = error.message;
                } else {
                    this.notifications.showAPIError(error, {key: 'staff.limit'});
                }
            }
        } else {
            this.limitErrorMessage = null;
        }
    }
}
<|endoftext|>


<commit_before>(function () {
  'use strict';

  describe('when transcluding with ngMultiTemplate', function () {
    beforeEach(module('toggles'));
    beforeEach(module('test-partials'));

    it('simple test', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();

      expect(el[0].querySelector('[ng-toggled]')).text().toBe('');

      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');

      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('');
    }));
  });
})();
<commit_msg>Add a bunch more tests.<commit_after>(function () {
  'use strict';

  var works = function(el, content){
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('');

      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe(content);

      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('');
  };

  describe('toggle tests', function () {
    beforeEach(module('toggles'));

    it('works for simple toggles', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();
      works(el, 'Foo content.');
    }));

    it('supports interpolation in toggle names', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope, {
        i: 'hello'
      });
      var scope = el.scope();
      scope.$apply();

      works(el, 'Foo content.');
    }));

    it('supports interpolation in toggled names', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope, {
        i: 'hello'
      });
      var scope = el.scope();
      scope.$apply();

      works(el, 'Foo content.');
    }));
  });

  describe('default tests', function(){
    beforeEach(module('toggles'));

    it('supports fixed default state', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();

      // Already open.
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');

      // Close.
      el[0].querySelector('[ng-toggle]').click();

      // Still works.
      works(el, 'Foo content.');
    }));

    it('supports evaluated default state (open)', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope, {
        d: true
      });
      var scope = el.scope();
      scope.$apply();

      // Already open.
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');

      // Close.
      el[0].querySelector('[ng-toggle]').click();

      // Still works.
      works(el, 'Foo content.');
    }));

    it('supports evaluated default state (closed)', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
');
      el = $compile(el)($rootScope, {
        d: false
      });
      var scope = el.scope();
      scope.$apply();
    }));
  });

  describe('group tests', function(){
    beforeEach(module('toggles'));

    it('should close other toggles in a group when one is opened', inject(function ($compile, $rootScope) {
      var el = angular.element('
Foo

Foo content.
' +
        '
Foo

Bar content.
'
      );
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();

      expect(el[0].querySelector('[ng-toggled="foo"]')).text().toBe('');
      expect(el[0].querySelector('[ng-toggled="bar"]')).text().toBe('');

      el[0].querySelector('[ng-toggle="foo"]').click();
      expect(el[0].querySelector('[ng-toggled="foo"]')).text().toBe('Foo content.');
      expect(el[0].querySelector('[ng-toggled="bar"]')).text().toBe('');

      el[0].querySelector('[ng-toggle="bar"]').click();
      expect(el[0].querySelector('[ng-toggled="foo"]')).text().toBe('');
      expect(el[0].querySelector('[ng-toggled="bar"]')).text().toBe('Bar content.');

      el[0].querySelector('[ng-toggle="bar"]').click();
      expect(el[0].querySelector('[ng-toggled="foo"]')).text().toBe('');
      expect(el[0].querySelector('[ng-toggled="bar"]')).text().toBe('');
    }));
  });

  describe('auto-close tests', function () {
    beforeEach(module('toggles'));

    it('click off test', inject(function ($compile, $rootScope) {
      var el = angular.element('Another thing
Foo

Foo content.
');
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();

      // Open the toggle, verify it is open.
      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');

      // Click something else, verify the toggle closes.
      el[0].querySelector('span').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('');
    }));

    it('click on test', inject(function ($compile, $rootScope) {
      var el = angular.element('Another thing
Foo

Foo content.
');
      el = $compile(el)($rootScope);
      var scope = el.scope();
      scope.$apply();

      // Open the toggle, verify it is open.
      el[0].querySelector('[ng-toggle]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');

      // Click the content, verify the toggle does not close.
      el[0].querySelector('[ng-toggled]').click();
      expect(el[0].querySelector('[ng-toggled]')).text().toBe('Foo content.');
    }));
  });
})();
<|endoftext|>


<commit_before>var currentSection;

function openSection(focusSection, override){
	if(userID || override){
		var sections = document.getElementsByTagName('section');
		var size = sections.length;
		for(var s = 0; s < size; s++){
			sections[s].style.display = 'none';
		}
		if(focusSection === 'login'){
			userID = null;
			displayMessage("You have been logged out.");
		}
		if(focusSection === 'profile'){
			loadProfile();
		}
		if(currentSection === 'profile'){
			updateProfile();
		}
		currentSection = focusSection;
		document.getElementById(focusSection).style.display = 'block';
	}
	else{
		if(currentSection === 'login'){
			openSection('about', true);
		}
		else{
			openSection('login', true);
		}
		//displayMessage("You must be logged in to continue");
	}
}

function toggleMenu(reveal){
	header = document.querySelector("header");
	if(reveal){
		classie.add(header,"smaller");
	}
	else{
		classie.remove(header,"smaller");
	}
}

function displayMessage(message){
	var output = document.getElementById('messageContent');
		output.innerHTML = message;
	$('#messageBox').removeClass('close');
	$('#messageBox').addClass('open');
}

function closeMessageBox(){
	$('#messageBox').removeClass('open');
	$('#messageBox').addClass('close');
}

function convertOptionTagName(tag, toReadable){
	var response;
	if(toReadable){
		//Not ready for this case yet.
	}
	else{
		response = tag.replace(/\s+/g, '-').toLowerCase();
	}
	return response;
}

function initCollapsingHeader() {
	window.addEventListener('scroll', function(e){
		var distanceY = window.pageYOffset || document.documentElement.scrollTop,
			shrinkOn = 50,
			header = document.querySelector("header");
		if(distanceY > shrinkOn) {
			classie.add(header,"smaller");
		}
		else{
			if(classie.has(header,"smaller")) {
				classie.remove(header,"smaller");
			}
		}
	});
}
window.onload = initCollapsingHeader();

console.log("LOADED utils.js");<commit_msg>Fixed nonlogin menu errors<commit_after>var currentSection = 'login';

function openSection(focusSection, override){
	if(userID || override){
		var sections = document.getElementsByTagName('section');
		var size = sections.length;
		for(var s = 0; s < size; s++){
			sections[s].style.display = 'none';
		}
		if(focusSection === 'login' && userID){
			userID = null;
			displayMessage("You have been logged out.");
		}
		if(focusSection === 'profile'){
			loadProfile();
		}
		if(currentSection === 'profile'){
			updateProfile();
		}
		currentSection = focusSection;
		document.getElementById(focusSection).style.display = 'block';
	}
	else{
		if(currentSection === 'login'){
			openSection('about', true);
		}
		else{
			openSection('login', true);
		}
		//displayMessage("You must be logged in to continue");
	}
}

function toggleMenu(reveal){
	header = document.querySelector("header");
	if(reveal){
		classie.add(header,"smaller");
	}
	else{
		classie.remove(header,"smaller");
	}
}

function displayMessage(message){
	var output = document.getElementById('messageContent');
		output.innerHTML = message;
	$('#messageBox').removeClass('close');
	$('#messageBox').addClass('open');
}

function closeMessageBox(){
	$('#messageBox').removeClass('open');
	$('#messageBox').addClass('close');
}

function convertOptionTagName(tag, toReadable){
	var response;
	if(toReadable){
		//Not ready for this case yet.
	}
	else{
		response = tag.replace(/\s+/g, '-').toLowerCase();
	}
	return response;
}

function initCollapsingHeader() {
	window.addEventListener('scroll', function(e){
		var distanceY = window.pageYOffset || document.documentElement.scrollTop,
			shrinkOn = 50,
			header = document.querySelector("header");
		if(distanceY > shrinkOn) {
			classie.add(header,"smaller");
		}
		else{
			if(classie.has(header,"smaller")) {
				classie.remove(header,"smaller");
			}
		}
	});
}
window.onload = initCollapsingHeader();

console.log("LOADED utils.js");<|endoftext|>


<commit_before>'use strict'

/**
 * atom-markdown-katex plugin for atom editor
 * By Yiyi Wang (shd101wyy)
 *
 */

let fs = require("fs"),
    temp = require('temp'),
    exec = require('child_process').exec,
    path = require('path'),
    less = require('less')

let parseMD = require('./md.js').parseMD,
    startMDPreview = require('./md-preview.js').startMDPreview

// console.log('0.4.1')

/*
 * This pdfIFrame is added to document.body once.
 * it is used to render parsed html content from markdown and then print to pdf.
 */
let pdfIFrame = document.createElement('iframe')
pdfIFrame.style.display = 'none';
pdfIFrame.id = 'atom-markdown-katex-pdf'
pdfIFrame.src = 'about:blank'
pdfIFrame.onload = function (){ // show print option when iframe finishes loading
  if (pdfIFrame.src === 'about:blank') return
  pdfIFrame.contentWindow.print()
}
document.body.appendChild(pdfIFrame)

// Automatically track and cleanup files at exit
temp.track();

// open new window to show rendered markdown html
function beginMarkdownKatexPreview() {
  // get current selected active editor
  let activeEditor = atom.workspace.getActiveTextEditor()

  // already activated
  if (activeEditor.markdownHtmlView) {
    return
  } else {
    startMDPreview(activeEditor)
  }
}

// referred from markdown-preview-view.coffee in the official Markdown Preview
function getTextEditorStyles() {
  let textEditorStyles = document.createElement("atom-styles")
  textEditorStyles.initialize(atom.styles)
  textEditorStyles.setAttribute("context", "atom-text-editor")
  document.body.appendChild(textEditorStyles)

  // Extract style elements content
  let style = Array.prototype.slice.apply(textEditorStyles.childNodes).map((styleElement) => styleElement.innerText)

  // delete element
  textEditorStyles.parentNode.removeChild(textEditorStyles)

  return style
}

function getMarkdownPreviewCSS() {
  let markdownPreviewRules = [],
      ruleRegExp = /\.markdown-katex-preview/,
      cssUrlRefExp = /url\(atom:\/\/markdown-katex-preview\/assets\/(.*)\)/

  for (let i = 0; i < document.styleSheets.length; i++) {
    let stylesheet = document.styleSheets[i]
    if (stylesheet.rules.length) {
      for (let j = 0; j < stylesheet.rules.length; j++) {
        let rule = stylesheet.rules[j]

        // We only need `.markdown-katex-preview` css
        if (rule.cssText.match(ruleRegExp)) {
          markdownPreviewRules.push(rule.cssText)
        }
      }
    }
  }

  return markdownPreviewRules.concat(getTextEditorStyles())
}

// get offline html content
// pass in htmlContent to callback function
function getOfflineHTMLContent(callback) {
  let editor = atom.workspace.getActiveTextEditor(),
      rootDirectoryPath = editor.rootDirectoryPath,
      textContent = editor.textContent,
      useGitHubStyle = atom.config.get('atom-markdown-katex.useGitHubStyle'),
      htmlContent = parseMD(textContent, rootDirectoryPath)

  htmlContent = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>${editor.getFileName()}</title>
      <meta charset="utf-8">
      <style> ${getMarkdownPreviewCSS().join('\n')} </style>
      <link rel="stylesheet"
            href="${path.resolve(__dirname, '../katex-style/katex.min.css')}">
    </head>
    <body class="markdown-katex-preview" data-use-github-style="${useGitHubStyle}">
    ${htmlContent}
    </body>
  </html>
  `
  callback(htmlContent)
}

// print PDF file
// using webkit
function saveAsPDF() {
  getOfflineHTMLContent(function(htmlContent) {
    temp.open({prefix: 'atom-markdown-katex', suffix: '.html'}, function(err, info) {
      if (err) {
        alert('Failed to save as pdf')
      } else {
        fs.write(info.fd, htmlContent, function() {
          // set the src last.
          pdfIFrame.src = info.path
        })
      }
    })
  })
}

// copy HTML to clipboard
function copyAsHTML() {
  let editor = atom.workspace.getActiveTextEditor(),
      rootDirectoryPath = editor.rootDirectoryPath,
      textContent = editor.textContent,
      useGitHubStyle = atom.config.get('atom-markdown-katex.useGitHubStyle'),
      htmlContent = parseMD(textContent, rootDirectoryPath)

  htmlContent = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>${editor.getFileName()}</title>
      <meta charset="utf-8">
      <style> ${getMarkdownPreviewCSS().join('\n')} </style>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
    </head>
    <body class="markdown-katex-preview" data-use-github-style="${useGitHubStyle}">
    ${htmlContent}
    </body>
  </html>
  `
  atom.clipboard.write(htmlContent)

  alert("HTML content has bee saved to clipboard")
}

// open HTML in browser
function openInBrowser() {
  getOfflineHTMLContent(function(htmlContent) {
    temp.open({prefix: 'atom-markdown-katex', suffix: '.html'}, function(err, info) {
      if (err) {
        throw err
      } else {
        fs.write(info.fd, htmlContent, function(error) {
          if (error){ throw error;}
          // open in browser
          let cmd = process.platform === 'win32' ? 'explorer' :
                    process.platform === 'darwin' ? 'open' : 'xdg-open'
          exec(cmd + ' ' + info.path)
        })
      }
    })
  })
}

// customize markdown preview css
function customizeCSS() {
  atom.workspace.open(path.resolve(__dirname, "../styles/custom.less"))
}

function activateFn() {
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:save-as-pdf", saveAsPDF)
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:copy-as-html", copyAsHTML)
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:open-in-browser", openInBrowser)

  atom.commands.add("atom-workspace", "markdown-katex-preview:customize-css", customizeCSS)
  atom.commands.add("atom-workspace", "markdown-katex-preview:toggle", beginMarkdownKatexPreview)
}

module.exports = {
  activate: activateFn
}
<commit_msg>fix issue #8<commit_after>'use strict'

/**
 * atom-markdown-katex plugin for atom editor
 * By Yiyi Wang (shd101wyy)
 *
 */

let fs = require("fs"),
    temp = require('temp'),
    exec = require('child_process').exec,
    path = require('path'),
    less = require('less')

let parseMD = require('./md.js').parseMD,
    startMDPreview = require('./md-preview.js').startMDPreview

// console.log('0.4.1')

/*
 * This pdfIFrame is added to document.body once.
 * it is used to render parsed html content from markdown and then print to pdf.
 */
let pdfIFrame = document.createElement('iframe')
pdfIFrame.style.display = 'none';
pdfIFrame.id = 'atom-markdown-katex-pdf'
pdfIFrame.src = 'about:blank'
pdfIFrame.onload = function (){ // show print option when iframe finishes loading
  if (pdfIFrame.src === 'about:blank') return
  pdfIFrame.contentWindow.print()
}
document.body.appendChild(pdfIFrame)

// Automatically track and cleanup files at exit
temp.track();

// open new window to show rendered markdown html
function beginMarkdownKatexPreview() {
  // get current selected active editor
  let activeEditor = atom.workspace.getActiveTextEditor()

  // already activated
  if (!activeEditor || activeEditor.markdownHtmlView) {
    return
  } else {
    startMDPreview(activeEditor)
  }
}

// referred from markdown-preview-view.coffee in the official Markdown Preview
function getTextEditorStyles() {
  let textEditorStyles = document.createElement("atom-styles")
  textEditorStyles.initialize(atom.styles)
  textEditorStyles.setAttribute("context", "atom-text-editor")
  document.body.appendChild(textEditorStyles)

  // Extract style elements content
  let style = Array.prototype.slice.apply(textEditorStyles.childNodes).map((styleElement) => styleElement.innerText)

  // delete element
  textEditorStyles.parentNode.removeChild(textEditorStyles)

  return style
}

function getMarkdownPreviewCSS() {
  let markdownPreviewRules = [],
      ruleRegExp = /\.markdown-katex-preview/,
      cssUrlRefExp = /url\(atom:\/\/markdown-katex-preview\/assets\/(.*)\)/

  for (let i = 0; i < document.styleSheets.length; i++) {
    let stylesheet = document.styleSheets[i]
    if (stylesheet.rules.length) {
      for (let j = 0; j < stylesheet.rules.length; j++) {
        let rule = stylesheet.rules[j]

        // We only need `.markdown-katex-preview` css
        if (rule.cssText.match(ruleRegExp)) {
          markdownPreviewRules.push(rule.cssText)
        }
      }
    }
  }

  return markdownPreviewRules.concat(getTextEditorStyles())
}

// get offline html content
// pass in htmlContent to callback function
function getOfflineHTMLContent(callback) {
  let editor = atom.workspace.getActiveTextEditor(),
      rootDirectoryPath = editor.rootDirectoryPath,
      textContent = editor.textContent,
      useGitHubStyle = atom.config.get('atom-markdown-katex.useGitHubStyle'),
      htmlContent = parseMD(textContent, rootDirectoryPath)

  htmlContent = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>${editor.getFileName()}</title>
      <meta charset="utf-8">
      <style> ${getMarkdownPreviewCSS().join('\n')} </style>
      <link rel="stylesheet"
            href="${path.resolve(__dirname, '../katex-style/katex.min.css')}">
    </head>
    <body class="markdown-katex-preview" data-use-github-style="${useGitHubStyle}">
    ${htmlContent}
    </body>
  </html>
  `
  callback(htmlContent)
}

// print PDF file
// using webkit
function saveAsPDF() {
  getOfflineHTMLContent(function(htmlContent) {
    temp.open({prefix: 'atom-markdown-katex', suffix: '.html'}, function(err, info) {
      if (err) {
        alert('Failed to save as pdf')
      } else {
        fs.write(info.fd, htmlContent, function() {
          // set the src last.
          pdfIFrame.src = info.path
        })
      }
    })
  })
}

// copy HTML to clipboard
function copyAsHTML() {
  let editor = atom.workspace.getActiveTextEditor(),
      rootDirectoryPath = editor.rootDirectoryPath,
      textContent = editor.textContent,
      useGitHubStyle = atom.config.get('atom-markdown-katex.useGitHubStyle'),
      htmlContent = parseMD(textContent, rootDirectoryPath)

  htmlContent = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>${editor.getFileName()}</title>
      <meta charset="utf-8">
      <style> ${getMarkdownPreviewCSS().join('\n')} </style>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
    </head>
    <body class="markdown-katex-preview" data-use-github-style="${useGitHubStyle}">
    ${htmlContent}
    </body>
  </html>
  `
  atom.clipboard.write(htmlContent)

  alert("HTML content has bee saved to clipboard")
}

// open HTML in browser
function openInBrowser() {
  getOfflineHTMLContent(function(htmlContent) {
    temp.open({prefix: 'atom-markdown-katex', suffix: '.html'}, function(err, info) {
      if (err) {
        throw err
      } else {
        fs.write(info.fd, htmlContent, function(error) {
          if (error){ throw error;}
          // open in browser
          let cmd = process.platform === 'win32' ? 'explorer' :
                    process.platform === 'darwin' ? 'open' : 'xdg-open'
          exec(cmd + ' ' + info.path)
        })
      }
    })
  })
}

// customize markdown preview css
function customizeCSS() {
  atom.workspace.open(path.resolve(__dirname, "../styles/custom.less"))
}

function activateFn() {
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:save-as-pdf", saveAsPDF)
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:copy-as-html", copyAsHTML)
  atom.commands.add(".markdown-katex-preview", "markdown-katex-preview:open-in-browser", openInBrowser)

  atom.commands.add("atom-workspace", "markdown-katex-preview:customize-css", customizeCSS)
  atom.commands.add("atom-workspace", "markdown-katex-preview:toggle", beginMarkdownKatexPreview)
}

module.exports = {
  activate: activateFn
}
<|endoftext|>


<commit_before>"use strict";

let constant = require("./constant.js"),
    exec     = require("./exec.js"),
    fs       = require("fs"),
    helper   = require("./helper.js"),
    path     = require("path");

const Bipbip = function(path, env) {
	if (exec.local("which rsync") == "rsync:\n") {
		helper.log("Missing rsync", "red");
		process.exit(1);
	}

	if (exec.local("which git") == "git:\n") {
		helper.log("Missing git", "red");
		process.exit(1);
	}

	// Prepare current configuration
	let fileConfig = require(path).config,
		envConfig = helper.merge(constant.default, fileConfig.default, fileConfig[env]),
		kill = function(message) {
			helper.log(message, "red");
			process.exit(1);
		};

	if (envConfig.servers === undefined) {
		kill("servers is required.");
	} else if (Array.isArray(envConfig.servers) === false) {
		kill("servers must be an array");
	}

	for (let i = envConfig.servers.length - 1; i >= 0; i--) {
		let server = envConfig.servers[i];

		if (server.user === undefined || typeof server.user != "string") {
			kill("servers." + i.toString() + ".user must be a string");
		} else if (server.host === undefined || typeof server.host != "string") {
			kill("servers." + i.toString() + ".host must be a string");
		} else if (server.to === undefined || typeof server.to != "string") {
			kill("servers." + i.toString() + ".to must be a string");
		}
	}

	if (typeof envConfig.repository === "object" && envConfig.repository !== null && envConfig.repository.url !== null && (envConfig.repository.branch === null && envConfig.repository.tag === null)) {
		kill("repository.branch or repository.tag is required.");
	}

	this.config = envConfig;
	this.internal = {
		release: exec.local('date +"%Y%d%m%H%M%S"').split("\n")[0]
	};
	this.file = path;
};

Bipbip.prototype.run = function() {
	if (this.config.repository !== null && this.config.repository.url !== null) {
		this.init();
	} else {
		this.config.workspace = path.dirname(this.file);
	}

	this.local();
	this.send(
		Bipbip.prototype.shared.bind(
			this,
			Bipbip.prototype.remote.bind(this)
		)
	);

	this.deploy();
	this.postDeploy();
	this.clean();
};

Bipbip.prototype.init = function() {
	if (fs.existsSync(this.config.workspace) === true) {
		try {
			exec.local("git rev-parse --git-dir", {
				cwd: this.config.workspace,
			});
		} catch (err) {
			fs.rmdirSync(this.config.workspace);
		}

		exec.local([
			"git reset --hard",
			"git fetch --all",
			"git fetch --tags"
		].join(" && "), {
			cwd: this.config.workspace,
		});

		if (this.config.repository.branch !== null) {
			exec.local(
				"git remote set-branches --add origin " + this.config.repository.branch, {
					cwd: this.config.workspace,
				});

			exec.local(
				"[ -n \"`git show-ref refs/heads/" + this.config.repository.branch + "`\" ] && git checkout -q " + this.config.repository.branch + " || git checkout -b " + this.config.repository.branch + " origin/" + this.config.repository.branch, {
				cwd: this.config.workspace,
			});

			exec.local(
				"git pull origin " + this.config.repository.branch, {
				cwd: this.config.workspace,
			});
		} else if (this.config.repository.tag !== null)  {
			exec.local(
				"git checkout -q refs/tags/" + this.config.repository.tag, {
				cwd: this.config.workspace,
			});

			exec.local(
				"git pull origin " + this.config.repository.tag, {
				cwd: this.config.workspace,
			});
		}
	} else {
		let gitOption = "";
		if (this.config.repository.options.submodules === true) {
			gitOption += " --recursive";
		}

		exec.local("git clone -b " + this.config.repository.branch + " " + this.config.repository.url + " --depth=1 " + this.config.workspace + gitOption);
	}
};

Bipbip.prototype.local = function() {
	if (this.config.commands.local.length > 0) {
		for (let i = 0; i < this.config.commands.local.length; i++) {
		    exec.local(this.config.commands.local[i], {
		    	cwd: this.config.workspace,
		    });
		}
	}
};

Bipbip.prototype.send = function(callback) {
	let _this = this;

	let _ = function(server) {
		let remotePath = server.to + "/releases/" + _this.internal.release;
		let lastRelease = _this._execRemote(server, "ls -1t " + server.to + "/releases | head -n 1");

		_this._execRemote(server, "mkdir -p " + server.to + "/releases");

		_this._execRemote(
			server,
			helper.mkdir(
				server.to + "/shared"
				));

		if (lastRelease !== "") {
			let mkdirRemotePath = [
				"cp -R " + server.to + "/releases/" + lastRelease,
				remotePath
				].join(" ");

			_this._execRemote(server, mkdirRemotePath);
		}

		let cmd = [
			"rsync",
			"-az",
			"--delete",
			_this.config.ignores.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.shared.files.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.shared.folders.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.workspace + "/",
			[
				[
					server.user, server.host].join("@"),
					remotePath
				].join(":")
			].join(" ");

		exec.local(cmd);
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.shared = function(callback) {
	let _this = this;

	let _ = function(server) {
		for (let i = _this.config.shared.folders.length - 1; i >= 0; i--) {
			_this._execRemote(
				server,
				helper.mkdir(
					server.to + "/shared/" + _this.config.shared.folders[i]
					)
				);

			_this._execRemote(
				server,
				helper.ln(
					server.to + "/shared/" + _this.config.shared.folders[i],
					server.to + "/releases/" + _this.internal.release + "/" + _this.config.shared.folders[i]
					)
				);
		}

		for (let i = _this.config.shared.files.length - 1; i >= 0; i--) {
			let folder = _this.config.shared.files[i].substr(
				0,
				_this.config.shared.files[i].lastIndexOf("/")
				);

			let file = _this.config.shared.files[i].substr(
				_this.config.shared.files[i].lastIndexOf("/")
				);

			_this._execRemote(
				server,
				helper.mkdir(
					server.to + "/shared/" + folder
					)
				);

			if (file != "/") {
				_this._execRemote(
					server,
					helper.touch(
						server.to + "/shared/" + folder + file
						)
					);

				_this._execRemote(
					server,
					helper.ln(
						server.to + "/shared/" + _this.config.shared.files[i],
						server.to + "/releases/" + _this.internal.release + "/" + _this.config.shared.files[i]
						)
					);
			} else {
				_this._execRemote(
					server,
					helper.ln(
						server.to + "/shared/" + folder,
						server.to + "/releases/" + _this.internal.release + "/" + folder
						)
					);
			}
		}
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.remote = function() {
	let _this = this;

	let _ = function(server) {
		if (_this.config.commands.remote.length > 0) {
			for (let i = 0; i < _this.config.commands.remote.length; i++) {
			    _this._execRemote(
			    	server,
			    	"cd " + server.to + "/releases/" + _this.internal.release + " && " + _this.config.commands.remote[i]
			    	);
			}
		}
	};

	for (let i = this.config.servers.length - 1; i >= 0; i--) {
		_(this.config.servers[i]);
	}
};

Bipbip.prototype.deploy = function() {
	let _this = this;

	let _ = function(server) {
		_this._execRemote(
			server,
			"rm -f " + server.to + "/current && ln -s " + server.to + "/releases/" + _this.internal.release + " " + server.to + "/current"
			);
	};

	for (let i = this.config.servers.length - 1; i >= 0; i--) {
		_(this.config.servers[i]);
	}
};

Bipbip.prototype.postDeploy = function() {
	let _this = this;

	let _ = function(server) {
		if (_this.config.commands.postDeploy.length > 0) {
			for (let i = 0; i < _this.config.commands.postDeploy.length; i++) {
			    _this._execRemote(
			    	server,
			    	"cd " + server.to + "/releases/" + _this.internal.release + " && " + _this.config.commands.postDeploy[i]
			    	);
			}
		}
	};

	for (let i = this.config.servers.length - 1; i >= 0; i--) {
		_(this.config.servers[i]);
	}
};

Bipbip.prototype.clean = function() {
	let _this = this;

	let _ = function(server) {
		_this._execRemote(
			server,
			"ls -1dt " + server.to + "/releases/* | tail -n +" + (_this.config.releases + 1).toString() + " | xargs rm -rf"
			);
	};

	for (let i = this.config.servers.length - 1; i >= 0; i--) {
		_(this.config.servers[i]);
	}
};

Bipbip.prototype._execRemote = function(server, command, escape) {
	return exec.remote(server, command, escape);
};

module.exports = Bipbip;
<commit_msg>[WIP] remote, deploy, postDeploy and clean methods use Promise<commit_after>"use strict";

let constant = require("./constant.js"),
    exec     = require("./exec.js"),
    fs       = require("fs"),
    helper   = require("./helper.js"),
    path     = require("path");

const Bipbip = function(path, env) {
	if (exec.local("which rsync") == "rsync:\n") {
		helper.log("Missing rsync", "red");
		process.exit(1);
	}

	if (exec.local("which git") == "git:\n") {
		helper.log("Missing git", "red");
		process.exit(1);
	}

	// Prepare current configuration
	let fileConfig = require(path).config,
		envConfig = helper.merge(constant.default, fileConfig.default, fileConfig[env]),
		kill = function(message) {
			helper.log(message, "red");
			process.exit(1);
		};

	if (envConfig.servers === undefined) {
		kill("servers is required.");
	} else if (Array.isArray(envConfig.servers) === false) {
		kill("servers must be an array");
	}

	for (let i = envConfig.servers.length - 1; i >= 0; i--) {
		let server = envConfig.servers[i];

		if (server.user === undefined || typeof server.user != "string") {
			kill("servers." + i.toString() + ".user must be a string");
		} else if (server.host === undefined || typeof server.host != "string") {
			kill("servers." + i.toString() + ".host must be a string");
		} else if (server.to === undefined || typeof server.to != "string") {
			kill("servers." + i.toString() + ".to must be a string");
		}
	}

	if (typeof envConfig.repository === "object" && envConfig.repository !== null && envConfig.repository.url !== null && (envConfig.repository.branch === null && envConfig.repository.tag === null)) {
		kill("repository.branch or repository.tag is required.");
	}

	this.config = envConfig;
	this.internal = {
		release: exec.local('date +"%Y%d%m%H%M%S"').split("\n")[0]
	};
	this.file = path;
};

Bipbip.prototype.run = function() {
	if (this.config.repository !== null && this.config.repository.url !== null) {
		this.init();
	} else {
		this.config.workspace = path.dirname(this.file);
	}

	this.local();
	this.send(
		Bipbip.prototype.shared.bind(
			this,
			Bipbip.prototype.remote.bind(
				this,
				Bipbip.prototype.deploy.bind(
					this,
					Bipbip.prototype.postDeploy.bind(
						this,
						Bipbip.prototype.clean.bind(this)
					)
				)
			)
		)
	);
};

Bipbip.prototype.init = function() {
	if (fs.existsSync(this.config.workspace) === true) {
		try {
			exec.local("git rev-parse --git-dir", {
				cwd: this.config.workspace,
			});
		} catch (err) {
			fs.rmdirSync(this.config.workspace);
		}

		exec.local([
			"git reset --hard",
			"git fetch --all",
			"git fetch --tags"
		].join(" && "), {
			cwd: this.config.workspace,
		});

		if (this.config.repository.branch !== null) {
			exec.local(
				"git remote set-branches --add origin " + this.config.repository.branch, {
					cwd: this.config.workspace,
				});

			exec.local(
				"[ -n \"`git show-ref refs/heads/" + this.config.repository.branch + "`\" ] && git checkout -q " + this.config.repository.branch + " || git checkout -b " + this.config.repository.branch + " origin/" + this.config.repository.branch, {
				cwd: this.config.workspace,
			});

			exec.local(
				"git pull origin " + this.config.repository.branch, {
				cwd: this.config.workspace,
			});
		} else if (this.config.repository.tag !== null)  {
			exec.local(
				"git checkout -q refs/tags/" + this.config.repository.tag, {
				cwd: this.config.workspace,
			});

			exec.local(
				"git pull origin " + this.config.repository.tag, {
				cwd: this.config.workspace,
			});
		}
	} else {
		let gitOption = "";
		if (this.config.repository.options.submodules === true) {
			gitOption += " --recursive";
		}

		exec.local("git clone -b " + this.config.repository.branch + " " + this.config.repository.url + " --depth=1 " + this.config.workspace + gitOption);
	}
};

Bipbip.prototype.local = function() {
	if (this.config.commands.local.length > 0) {
		for (let i = 0; i < this.config.commands.local.length; i++) {
		    exec.local(this.config.commands.local[i], {
		    	cwd: this.config.workspace,
		    });
		}
	}
};

Bipbip.prototype.send = function(callback) {
	let _this = this;

	let _ = function(server) {
		let remotePath = server.to + "/releases/" + _this.internal.release;
		let lastRelease = _this._execRemote(server, "ls -1t " + server.to + "/releases | head -n 1");

		_this._execRemote(server, "mkdir -p " + server.to + "/releases");

		_this._execRemote(
			server,
			helper.mkdir(
				server.to + "/shared"
				));

		if (lastRelease !== "") {
			let mkdirRemotePath = [
				"cp -R " + server.to + "/releases/" + lastRelease,
				remotePath
				].join(" ");

			_this._execRemote(server, mkdirRemotePath);
		}

		let cmd = [
			"rsync",
			"-az",
			"--delete",
			_this.config.ignores.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.shared.files.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.shared.folders.map(function (item) {
				return "--exclude=" + item;
			}).join(" "),
			_this.config.workspace + "/",
			[
				[
					server.user, server.host].join("@"),
					remotePath
				].join(":")
			].join(" ");

		exec.local(cmd);
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.shared = function(callback) {
	let _this = this;

	let _ = function(server) {
		for (let i = _this.config.shared.folders.length - 1; i >= 0; i--) {
			_this._execRemote(
				server,
				helper.mkdir(
					server.to + "/shared/" + _this.config.shared.folders[i]
					)
				);

			_this._execRemote(
				server,
				helper.ln(
					server.to + "/shared/" + _this.config.shared.folders[i],
					server.to + "/releases/" + _this.internal.release + "/" + _this.config.shared.folders[i]
					)
				);
		}

		for (let i = _this.config.shared.files.length - 1; i >= 0; i--) {
			let folder = _this.config.shared.files[i].substr(
				0,
				_this.config.shared.files[i].lastIndexOf("/")
				);

			let file = _this.config.shared.files[i].substr(
				_this.config.shared.files[i].lastIndexOf("/")
				);

			_this._execRemote(
				server,
				helper.mkdir(
					server.to + "/shared/" + folder
					)
				);

			if (file != "/") {
				_this._execRemote(
					server,
					helper.touch(
						server.to + "/shared/" + folder + file
						)
					);

				_this._execRemote(
					server,
					helper.ln(
						server.to + "/shared/" + _this.config.shared.files[i],
						server.to + "/releases/" + _this.internal.release + "/" + _this.config.shared.files[i]
						)
					);
			} else {
				_this._execRemote(
					server,
					helper.ln(
						server.to + "/shared/" + folder,
						server.to + "/releases/" + _this.internal.release + "/" + folder
						)
					);
			}
		}
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.remote = function(callback) {
	let _this = this;

	let _ = function(server) {
		if (_this.config.commands.remote.length > 0) {
			for (let i = 0; i < _this.config.commands.remote.length; i++) {
			    _this._execRemote(
			    	server,
			    	"cd " + server.to + "/releases/" + _this.internal.release + " && " + _this.config.commands.remote[i]
			    	);
			}
		}
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.deploy = function(callback) {
	let _this = this;

	let _ = function(server) {
		_this._execRemote(
			server,
			"rm -f " + server.to + "/current && ln -s " + server.to + "/releases/" + _this.internal.release + " " + server.to + "/current"
			);
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.postDeploy = function(callback) {
	let _this = this;

	let _ = function(server) {
		if (_this.config.commands.postDeploy.length > 0) {
			for (let i = 0; i < _this.config.commands.postDeploy.length; i++) {
			    _this._execRemote(
			    	server,
			    	"cd " + server.to + "/releases/" + _this.internal.release + " && " + _this.config.commands.postDeploy[i]
			    	);
			}
		}
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
			callback();
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype.clean = function() {
	let _this = this;

	let _ = function(server) {
		_this._execRemote(
			server,
			"ls -1dt " + server.to + "/releases/* | tail -n +" + (_this.config.releases + 1).toString() + " | xargs rm -rf"
			);
	};

	let promises = this.config.servers.map((server) => {
		return new Promise((resolve, reject) => {
			_(server);

			resolve();
		});
	});

	Promise
		.all(promises)
		.then((content) => {
		}).catch((error) => {
			console.error(error);
		});
};

Bipbip.prototype._execRemote = function(server, command, escape) {
	return exec.remote(server, command, escape);
};

module.exports = Bipbip;
<|endoftext|>


<commit_before>
describe('Movies', function () {
    'use strict';
    var baseUrl = '/movies';
    var addMovieUrl = '/movies/new';

    beforeEach(function () {
        var deleteMovie = function () {
            browser().navigateTo(baseUrl);
            element('table tbody').query(function (tbody, done) {
                var children = tbody.children();

                if (children.length > 0) {
                    element('table tbody a').click();
                    element('.btn-danger').click();
                }

                if (children.length > 1) {
                    deleteMovie();
                }

                done();
            });

        };

        deleteMovie();
        browser().navigateTo(baseUrl);
    });

    it('should be accessible', function () {
        expect(element('h1').text()).toEqual('Movie List');
    });

    it('should allow adding of movies', function () {
        element('.btn-primary').click();
        expect(browser().window().path()).toEqual('/movies/new');
    });

    it('should add movies and forward to the detail view', function () {
        var title = 'Batman: The Dark Knight';
        var description = 'When Batman, Gordon and Harvey Dent...';

        // add movie
        browser().navigateTo(addMovieUrl);
        input('movie.title').enter(title);
        input('movie.description').enter(description);
        element('.btn-primary').click();

        // make sure it has been saved
        expect(browser().window().path()).toMatch(/^\/movies\/.*$/);
        expect(element('h2').text()).toEqual(title);

        // look for it on the overview page
        browser().navigateTo(baseUrl);
        expect(repeater('table tbody tr').count()).toBeGreaterThan(0);
    });
});<commit_msg>chore(Test data): Add more test data for the integration tests<commit_after>
describe('Movies', function () {
    'use strict';
    var baseUrl = '/movies';
    var addMovieUrl = '/movies/new';

    beforeEach(function () {
        var deleteMovie = function () {
            browser().navigateTo(baseUrl);
            element('table tbody').query(function (tbody, done) {
                var children = tbody.children();

                if (children.length > 0) {
                    element('table tbody a').click();
                    element('.btn-danger').click();
                }

                if (children.length > 1) {
                    deleteMovie();
                }

                done();
            });

        };

        deleteMovie();
        browser().navigateTo(baseUrl);
    });

    it('should be accessible', function () {
        expect(element('h1').text()).toEqual('Movie List');
    });

    it('should allow adding of movies', function () {
        element('.btn-primary').click();
        expect(browser().window().path()).toEqual('/movies/new');
    });

    function addMovie (title, description) {
        browser().navigateTo(addMovieUrl);
        input('movie.title').enter(title);
        input('movie.description').enter(description);
        element('.btn-primary').click();
    }

    function addTestMovies () {
        // The short description of each of the following movies was copied
        // from the IMDB. [Insert further copyright notices here] ;-)

        addMovie('The Dark Knight', 'When Batman, Gordon and Harvey Dent ' +
            'launch an assault on the mob, they let the clown out of the ' +
            'box, the Joker, bent on turning Gotham on itself and bringing ' +
            'any heroes down to his level.');
        addMovie('The Dark Knight Rises', 'Eight years on, a new evil rises ' +
            'from where the Batman and Commissioner Gordon tried to bury it, ' +
            'causing the Batman to resurface and fight to protect Gotham ' +
            'City... the very city which brands him an enemy.');
        addMovie('Cloud Atlas', 'An exploration of how the actions of ' +
            'individual lives impact one another in the past, present and ' +
            'future, as one soul is shaped from a killer into a hero, and ' +
            'an act of kindness ripples across centuries to inspire a ' +
            'revolution.');
        addMovie('Ted', 'As the result of a childhood wish, John Bennett\'s ' +
            'teddy bear, Ted, came to life and has been by John\'s side ever ' +
            'since - a friendship that\'s tested when Lori, John\'s ' +
            'girlfriend of four years, wants more from their relationship.');
        addMovie('The Meerkats', 'An inspiring exploration of one family\'s ' +
            'resilience and fortitude shot using innovative and ' +
            'groundbreaking filming techniques.');
        addMovie('Undisputed II: Last Man Standing', 'Sequel to the 2002 ' +
            'film. This time, Heavyweight Champ George "Iceman" Chambers is ' +
            'sent to a Russian Jail on trumped-up drug charges.');
    }

    it('should add movies and forward to the detail view', function () {
        addTestMovies();

        // look for it on the overview page
        browser().navigateTo(baseUrl);
        expect(repeater('table tbody tr').count()).toBeGreaterThan(0);
    });
});
<|endoftext|>


<commit_before>"use strict";

var should = require("should");
var request = require("supertest");
var express = require("express");
var expressBatch = require("..");

describe("request to route for express-batch", function () {

    var app;

    beforeEach(function createApp() {
        app = express();
        app.use("/api/batch", expressBatch(app));
    });

    describe("without any api endpoint specified", function () {
        it("should return empty object", function (done) {
            request(app)
                .get("/api/batch")
                .expect({
                })
                .expect(200, done);
        });
    });

    describe("with invalid endpoint specified", function () {
        it("should indicate 'not found' status in result", function (done) {
            request(app)
                .get("/api/batch?endpoint=/wrong/path")
                .expect({
                    endpoint: {
                        status: 404,
                        result: "Not Found"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with request to endpoint handler with async exception", function () {
        it("should handle exception and return status 500 in result", function (done) {

            app.get("/api/exception/sync", function (req, res) {
                throw new Error('sync exception');
            });

            request(app)
                .get("/api/batch?endpoint=/api/exception/sync")
                .expect({
                    endpoint: {
                        status: 500,
                        result: "Internal Server Error"
                    }
                })
                .expect(200, done);
        });
    });

    describe.skip("with request to endpoint handler with async exception", function () {
        it("should handle exception and return status 500 in result", function (done) {

            app.get("/api/exception/async", function (req, res) {
                setImmediate(function asyncException() {
                    throw new Error('async exception');
                });

            });

            request(app)
                .get("/api/batch?endpoint=/api/exception/async")
                .expect({
                    endpoint: {
                        status: 500
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.json", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.json({
                    id: 17
                });
            });

            request(app)
                .get("/api/batch?user=/api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 17
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.jsonp", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.jsonp({
                    id: 2
                });
            });

            request(app)
                .get("/api/batch?user=/api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 2
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.send method", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.send(556984800);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 200,
                        result: 556984800
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.end method", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.end(556984800);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 200,
                        result: 556984800
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.sendFile method", function () {
        it("should return error status for this endpoint since it isn't supported", function (done) {
            app.get("/api/file", function apiFileHandler(req, res) {
                res.sendFile(__filename);
            });

            request(app)
                .get("/api/batch?file=/api/file")
                .expect({
                    file: {
                        status: 501,
                        result: "Not Implemented"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, uses res.status and res.end methods", function () {
        it("should return only status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.status(403).end();
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 403
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which specified status only via res.sendStatus using", function () {
        it("should return only status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.sendStatus(403);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 403,
                        result: "Forbidden"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint without leading slash", function () {
        it("should return result for this endpoint anyway", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.json({
                    id: 41
                });
            });

            request(app)
                .get("/api/batch?user=api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 41
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with two endpoints specified", function () {
        it("should return results for both endpoints", function (done) {
            app
                .get("/api/president/:id", function apiUserHandler(req, res) {
                    res.json({
                        id: 44,
                        name: 'Barack'
                    });
                })
                .get("/api/weather/:city/:timestamp", function apiUserHandler(req, res) {
                    res.json({
                        city: 'Kyiv',
                        timestamp: 1416337310,
                        temperature: -2,
                        unit: '°C'
                    });
                });

            request(app)
                .get("/api/batch?president=api/president/44&weather=/api/weather/kyiv/1416337310")
                .expect({
                    president: {
                        status: 200,
                        result: {
                            id: 44,
                            name: 'Barack'
                        }
                    },
                    weather: {
                        status: 200,
                        result: {
                            city: 'Kyiv',
                            timestamp: 1416337310,
                            temperature: -2,
                            unit: '°C'
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with three endpoints specified, when one of them not found", function () {
        it("should return results for two endpoints and status for not existent", function (done) {
            app
                .get("/api/constants/pi", function apiUserHandler(req, res) {
                    res.send(Math.PI);
                })
                .get("/api/constants/e", function apiUserHandler(req, res) {
                    res.send(Math.E);
                });

            request(app)
                .get("/api/batch?e=/api/constants/e&pi=/api/constants/pi&mendelson=/api/constants/mendelson")
                .expect({
                    e: {
                        status: 200,
                        result: Math.E
                    },
                    pi: {
                        status: 200,
                        result: Math.PI
                    },
                    mendelson: {
                        status: 404,
                        result: "Not Found"
                    }                })
                .expect(200, done);
        });
    });
});<commit_msg>restored test for module interface<commit_after>"use strict";

var should = require("should");
var request = require("supertest");
var express = require("express");
var expressBatch = require("..");

describe("express-batch as a module", function () {
    it("should provide function, which returns middleware function", function () {
        var app = express();
        should(expressBatch).be.a.Function;

        should(expressBatch(app)).be.a.Function;
    });
});

describe("request to route for express-batch", function () {

    var app;

    beforeEach(function createApp() {
        app = express();
        app.use("/api/batch", expressBatch(app));
    });

    describe("without any api endpoint specified", function () {
        it("should return empty object", function (done) {
            request(app)
                .get("/api/batch")
                .expect({
                })
                .expect(200, done);
        });
    });

    describe("with invalid endpoint specified", function () {
        it("should indicate 'not found' status in result", function (done) {
            request(app)
                .get("/api/batch?endpoint=/wrong/path")
                .expect({
                    endpoint: {
                        status: 404,
                        result: "Not Found"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with request to endpoint handler with async exception", function () {
        it("should handle exception and return status 500 in result", function (done) {

            app.get("/api/exception/sync", function (req, res) {
                throw new Error('sync exception');
            });

            request(app)
                .get("/api/batch?endpoint=/api/exception/sync")
                .expect({
                    endpoint: {
                        status: 500,
                        result: "Internal Server Error"
                    }
                })
                .expect(200, done);
        });
    });

    describe.skip("with request to endpoint handler with async exception", function () {
        it("should handle exception and return status 500 in result", function (done) {

            app.get("/api/exception/async", function (req, res) {
                setImmediate(function asyncException() {
                    throw new Error('async exception');
                });

            });

            request(app)
                .get("/api/batch?endpoint=/api/exception/async")
                .expect({
                    endpoint: {
                        status: 500
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.json", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.json({
                    id: 17
                });
            });

            request(app)
                .get("/api/batch?user=/api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 17
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.jsonp", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.jsonp({
                    id: 2
                });
            });

            request(app)
                .get("/api/batch?user=/api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 2
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.send method", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.send(556984800);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 200,
                        result: 556984800
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.end method", function () {
        it("should return result and status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.end(556984800);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 200,
                        result: 556984800
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which uses res.sendFile method", function () {
        it("should return error status for this endpoint since it isn't supported", function (done) {
            app.get("/api/file", function apiFileHandler(req, res) {
                res.sendFile(__filename);
            });

            request(app)
                .get("/api/batch?file=/api/file")
                .expect({
                    file: {
                        status: 501,
                        result: "Not Implemented"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, uses res.status and res.end methods", function () {
        it("should return only status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.status(403).end();
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 403
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint, which specified status only via res.sendStatus using", function () {
        it("should return only status for this endpoint", function (done) {
            app.get("/api/timestamp", function apiTimestampHandler(req, res) {
                res.sendStatus(403);
            });

            request(app)
                .get("/api/batch?timestamp=/api/timestamp")
                .expect({
                    timestamp: {
                        status: 403,
                        result: "Forbidden"
                    }
                })
                .expect(200, done);
        });
    });

    describe("with specified path to endpoint without leading slash", function () {
        it("should return result for this endpoint anyway", function (done) {
            app.get("/api/user", function apiUserHandler(req, res) {
                res.json({
                    id: 41
                });
            });

            request(app)
                .get("/api/batch?user=api/user")
                .expect({
                    user: {
                        status: 200,
                        result: {
                            id: 41
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with two endpoints specified", function () {
        it("should return results for both endpoints", function (done) {
            app
                .get("/api/president/:id", function apiUserHandler(req, res) {
                    res.json({
                        id: 44,
                        name: 'Barack'
                    });
                })
                .get("/api/weather/:city/:timestamp", function apiUserHandler(req, res) {
                    res.json({
                        city: 'Kyiv',
                        timestamp: 1416337310,
                        temperature: -2,
                        unit: '°C'
                    });
                });

            request(app)
                .get("/api/batch?president=api/president/44&weather=/api/weather/kyiv/1416337310")
                .expect({
                    president: {
                        status: 200,
                        result: {
                            id: 44,
                            name: 'Barack'
                        }
                    },
                    weather: {
                        status: 200,
                        result: {
                            city: 'Kyiv',
                            timestamp: 1416337310,
                            temperature: -2,
                            unit: '°C'
                        }
                    }
                })
                .expect(200, done);
        });
    });

    describe("with three endpoints specified, when one of them not found", function () {
        it("should return results for two endpoints and status for not existent", function (done) {
            app
                .get("/api/constants/pi", function apiUserHandler(req, res) {
                    res.send(Math.PI);
                })
                .get("/api/constants/e", function apiUserHandler(req, res) {
                    res.send(Math.E);
                });

            request(app)
                .get("/api/batch?e=/api/constants/e&pi=/api/constants/pi&mendelson=/api/constants/mendelson")
                .expect({
                    e: {
                        status: 200,
                        result: Math.E
                    },
                    pi: {
                        status: 200,
                        result: Math.PI
                    },
                    mendelson: {
                        status: 404,
                        result: "Not Found"
                    }                })
                .expect(200, done);
        });
    });
});<|endoftext|>