<commit_before>!function ($) {
	"use strict";

	function inverse(valid){
		switch (valid) {
			case 'success': return 'danger';
			case 'warning': return 'danger';
			case 'danger': return 'success';
			case 'reset': return 'reset';
		}
		return (valid === undefined)? valid : !valid;
	}

	$.fn.bootstrap = function(options){

		options = options || {};
		var input = $(this);
		var parent1, parent2, parent3, group;

		if (options.status === 'progress') return;

		// manage feedback
		if (options.status === 'validating') {
			input.feedback({state: true});
		} else if (options.status === 'validated'){
			input.feedback({state: false});
		}

		if (options.status === 'validating') return;

		// add success class on options.validation = 'success'.
		// add failure class on options.validation = 'danger'.
		// remove success and failure classes on options.validation = 'reset'
		var tinsel = {
			validation: options.validation,
			classSuccess: 'has-success',
			classFailure: 'has-error',
			classWarning: 'has-warning'
		};

		// show message on options.validation = 'danger'.
		// remove message on options.valid = 'success'.
		// remove message on options.valid = 'reset'.
		var garland = {
			validation: inverse(options.validation),
			wrapper: '',
			message: options.message
		};

		parent1 = input.parent();
		parent2 = parent1.parent();
		parent3 = parent2.parent();

		//placement
		if (parent1.is('.form-group')){
			parent1.garland(garland);
			parent1.tinsel(tinsel);
		} else if (parent1.is('[class^="col-"]')){
			parent1.garland(garland);
			parent2.tinsel(tinsel);
		} else if (parent1.is('td')) {
			parent1.garland(garland);
			parent1.tinsel(tinsel);
		} else if (parent1.is('.checkbox') || parent1.is('.radio')){
			parent2.garland(garland);
			parent2.tinsel(tinsel);
		} else if (parent1.is('.input-group')){
			parent2.garland(garland);
			if (parent2.is('[class^="col-"]')) {
				parent3.tinsel(tinsel);
			} else {
				parent2.tinsel(tinsel);
			}
		} else if (parent3.is('[class^="col-"]')){
			parent3.garland(garland);
			input.closest('.form-group').tinsel(tinsel);
		} else {
			group = input.closest('.form-group');
			if (group.length) {
				input.closest('.form-group').garland(garland);
				input.closest('.form-group').tinsel(tinsel);
			} else {
				parent3.garland(garland);
				parent3.tinsel(tinsel);
			}
		}
	};
}(window.jQuery);
<commit_msg>Bootstrap decorator uses 'parentContainer' and 'errorContainer' options<commit_after>!function ($) {
	"use strict";

	function inverse(valid){
		switch (valid) {
			case 'success': return 'danger';
			case 'warning': return 'danger';
			case 'danger': return 'success';
			case 'reset': return 'reset';
		}
		return (valid === undefined)? valid : !valid;
	}

	$.fn.bootstrap = function(options){

		options = options || {};
		var input = $(this);
		var parent1, parent2, parent3, parentContainer, errorContainer, group;

		if (options.status === 'progress') return;

		// manage feedback
		if (options.status === 'validating') {
			input.feedback({state: true});
		} else if (options.status === 'validated'){
			input.feedback({state: false});
		}

		if (options.status === 'validating') return;

		// add success class on options.validation = 'success'.
		// add failure class on options.validation = 'danger'.
		// remove success and failure classes on options.validation = 'reset'
		var tinsel = {
			validation: options.validation,
			classSuccess: 'has-success',
			classFailure: 'has-error',
			classWarning: 'has-warning'
		};

		// show message on options.validation = 'danger'.
		// remove message on options.valid = 'success'.
		// remove message on options.valid = 'reset'.
		var garland = {
			validation: inverse(options.validation),
			wrapper: '',
			message: options.message
		};

		parent1 = input.parent();
		parent2 = parent1.parent();
		parent3 = parent2.parent();

		//placement
		if (parent1.is('.form-group')){
			errorContainer = parent1;
			parentContainer = parent1;
		} else if (parent1.is('[class^="col-"]')){
			errorContainer = parent1;
			parentContainer = parent2;
		} else if (parent1.is('td')) {
			errorContainer = parent1;
			parentContainer = parent1;
		} else if (parent1.is('.checkbox') || parent1.is('.radio')){
			errorContainer = parent2;
			parentContainer = parent2;
		} else if (parent1.is('.input-group')){
			errorContainer = parent2;
			if (parent2.is('[class^="col-"]')) {
				parentContainer = parent3;
			} else {
				parentContainer = parent2;
			}
		} else if (parent3.is('[class^="col-"]')){
			errorContainer = parent3;
			parentContainer = input.closest('.form-group');
		} else {
			group = input.closest('.form-group');
			if (group.length) {
				errorContainer = input.closest('.form-group');
				parentContainer = input.closest('.form-group');
			} else {
				errorContainer = parent3;
				parentContainer = parent3;
			}
		}

		if (options.parentContainer) parentContainer = $(options.parentContainer);
		if (options.errorContainer) errorContainer = $(options.errorContainer);

		errorContainer.garland(garland);
		parentContainer.tinsel(tinsel);
	};
}(window.jQuery);
<|endoftext|>


<commit_before>var fs = require( 'fs' );
var through2 = require('through2');
var map_stream = require('through2-map');
var filter_stream = require('through2-filter');
var _ = require('lodash');
var util = require('util');

/*
  this regex is used to test/match strings from WOF meta files that can take
  any of the forms:
  - 856/338/13/85633813.geojson
  - data/856/338/13/85633813.geojson
  - /usr/local/mapzen/whosonfirst/data/856/338/13/85633813.geojson

*/
var validDataFilePath = /([0-9]+\/[0-9]+\/[0-9]+\/[0-9]+\.geojson)$/;

/*
  returns true if record.path is a valid dataFilePath
*/
var is_valid_data_file_path = function is_valid_data_file_path() {
  return filter_stream.obj(function(record) {
    return validDataFilePath.test(record.path);
  });
};

/*
  this function extracts the last portion of the path for a record, eg:
  'data/856/338/13/85633813.geojson' returns '856/338/13/85633813.geojson'
*/
var normalize_file_path = function normalize_file_path() {
  return map_stream.obj(function(record) {
    return record.path.match(validDataFilePath)[1];
  });
};

/*
  this funciton verifies that a file exists and is readable
*/
var file_is_readable = function file_is_readable(dataDirectory) {
  return filter_stream.obj(function(filename) {
    try {
      fs.accessSync(dataDirectory + filename, fs.R_OK);
      return true;
    }
    catch (err) {
      console.error(util.format('data file cannot be read: %s', dataDirectory + filename));
      return false;
    }
  });
};

/*
  This function converts a file to an object (JSON-parsed)
*/
var json_parse_stream = function create_json_parse_stream(dataDirectory) {
  return map_stream.obj(function(filename) {
    return JSON.parse(fs.readFileSync(dataDirectory + filename));
  });
};

/*
  This function filters out incomplete records
*/
var filter_incomplete_files_stream = function create_filter_bad_files_stream() {
  return filter_stream.obj(function(json_object) {
    return json_object.id && json_object.hasOwnProperty('properties');
  });
};

/*
  This function extracts the fields from the json_object that we're interested
  in for creating Pelias Document objects.  If there is no hierarchy then a
  hierarchy-less object is added.  If there are multiple hierarchies for the
  record then a record for each hierarchy is pushed onto the stream.
*/
var map_fields_stream = function map_fields_stream() {
  return through2.obj(function(json_object, enc, callback) {
    var base_record = {
      id: json_object.id,
      name: json_object.properties['wof:name'],
      abbreviation: json_object.properties['wof:abbreviation'],
      place_type: json_object.properties['wof:placetype'],
      parent_id: json_object.properties['wof:parent_id'],
      lat: json_object.properties['geom:latitude'],
      lon: json_object.properties['geom:longitude'],
      bounding_box: json_object.properties['geom:bbox'],
      iso2: json_object.properties['iso:country']
    };

    // if there's no hierarchy then just add the base record
    if (_.isUndefined(json_object.properties['wof:hierarchy'])) {
      this.push(base_record);

    } else {
      // otherwise, clone the base record for each hierarchy in the list and push
      json_object.properties['wof:hierarchy'].forEach(function(hierarchy) {
        var clone = _.clone(base_record, true);
        clone.hierarchy = hierarchy;
        this.push(clone);
      }, this);

    }

    return callback();

  });

};

module.exports = {
  is_valid_data_file_path: is_valid_data_file_path,
  normalize_file_path: normalize_file_path,
  file_is_readable: file_is_readable,
  json_parse_stream: json_parse_stream,
  filter_incomplete_files_stream: filter_incomplete_files_stream,
  map_fields_stream: map_fields_stream
};
<commit_msg>fixed comment<commit_after>var fs = require( 'fs' );
var through2 = require('through2');
var map_stream = require('through2-map');
var filter_stream = require('through2-filter');
var _ = require('lodash');
var util = require('util');

/*
  this regex is used to test/match strings from WOF meta files that can take
  any of the forms:
  - 856/338/13/85633813.geojson
  - data/856/338/13/85633813.geojson
  - /usr/local/mapzen/whosonfirst/data/856/338/13/85633813.geojson

*/
var validDataFilePath = /([0-9]+\/[0-9]+\/[0-9]+\/[0-9]+\.geojson)$/;

/*
  returns true if record.path is a valid dataFilePath
*/
var is_valid_data_file_path = function is_valid_data_file_path() {
  return filter_stream.obj(function(record) {
    return validDataFilePath.test(record.path);
  });
};

/*
  this function extracts the last portion of the path for a record, eg:
  'data/856/338/13/85633813.geojson' returns '856/338/13/85633813.geojson'
*/
var normalize_file_path = function normalize_file_path() {
  return map_stream.obj(function(record) {
    return record.path.match(validDataFilePath)[1];
  });
};

/*
  this function verifies that a file exists and is readable, logs an error otherwise
*/
var file_is_readable = function file_is_readable(dataDirectory) {
  return filter_stream.obj(function(filename) {
    try {
      fs.accessSync(dataDirectory + filename, fs.R_OK);
      return true;
    }
    catch (err) {
      console.error(util.format('data file cannot be read: %s', dataDirectory + filename));
      return false;
    }
  });
};

/*
  This function converts a file to an object (JSON-parsed)
*/
var json_parse_stream = function create_json_parse_stream(dataDirectory) {
  return map_stream.obj(function(filename) {
    return JSON.parse(fs.readFileSync(dataDirectory + filename));
  });
};

/*
  This function filters out incomplete records
*/
var filter_incomplete_files_stream = function create_filter_bad_files_stream() {
  return filter_stream.obj(function(json_object) {
    return json_object.id && json_object.hasOwnProperty('properties');
  });
};

/*
  This function extracts the fields from the json_object that we're interested
  in for creating Pelias Document objects.  If there is no hierarchy then a
  hierarchy-less object is added.  If there are multiple hierarchies for the
  record then a record for each hierarchy is pushed onto the stream.
*/
var map_fields_stream = function map_fields_stream() {
  return through2.obj(function(json_object, enc, callback) {
    var base_record = {
      id: json_object.id,
      name: json_object.properties['wof:name'],
      abbreviation: json_object.properties['wof:abbreviation'],
      place_type: json_object.properties['wof:placetype'],
      parent_id: json_object.properties['wof:parent_id'],
      lat: json_object.properties['geom:latitude'],
      lon: json_object.properties['geom:longitude'],
      bounding_box: json_object.properties['geom:bbox'],
      iso2: json_object.properties['iso:country']
    };

    // if there's no hierarchy then just add the base record
    if (_.isUndefined(json_object.properties['wof:hierarchy'])) {
      this.push(base_record);

    } else {
      // otherwise, clone the base record for each hierarchy in the list and push
      json_object.properties['wof:hierarchy'].forEach(function(hierarchy) {
        var clone = _.clone(base_record, true);
        clone.hierarchy = hierarchy;
        this.push(clone);
      }, this);

    }

    return callback();

  });

};

module.exports = {
  is_valid_data_file_path: is_valid_data_file_path,
  normalize_file_path: normalize_file_path,
  file_is_readable: file_is_readable,
  json_parse_stream: json_parse_stream,
  filter_incomplete_files_stream: filter_incomplete_files_stream,
  map_fields_stream: map_fields_stream
};
<|endoftext|>


<commit_before>(function($) {
    tinymce.PluginManager.add('blockquote', function(editor, url) {

        var checkFormatMatch = function(alignment, ctrl) {

            // Check if the selection matches the format
            var formatMatch = editor.formatter.match('cc_blockquote_format');

            // Some magic to find the blockquote element from inside the selection
            var $selectedElement = $(editor.selection.getNode());

            if ($selectedElement.is('blockquote')) {
                $blockquote = $selectedElement;
            } else {
                $blockquote = $selectedElement.closest('blockquote');
            }

            var alignmentMatch = $blockquote.hasClass('cc-blockquote-' + alignment);
            var borderElementMatch = $blockquote.find('.cc-blockquote-border').length;
            var imageElementMatch = $blockquote.hasClass('cc-blockquote-image');

            // If all conditions are true, the button should be in its active state
            ctrl.active( formatMatch && alignmentMatch && (borderElementMatch || imageElementMatch) );

        };

        var toggleBlockquoteFormat = function(alignment) {

            if (!editor.formatter.match('cc_blockquote_format')) {

                // If the blockquote format is not already applied to the element, we apply it before doing anything else.
                editor.formatter.apply('cc_blockquote_format');

                // Some magic to find the blockquote element from inside the selection
                var $selectedElement = $(editor.selection.getNode());

                if ($selectedElement.is('blockquote')) {
                    $blockquote = $selectedElement;
                } else {
                    $blockquote = $selectedElement.closest('blockquote');
                }

                $blockquote.addClass('cc-blockquote-' + alignment);

                var $img = $blockquote.find('img');
                if ($img.length) {
                    $blockquote.addClass('cc-blockquote-image');
                } else {
                    $blockquote.addClass('cc-blockquote-text');

                    // Check whether or not we already have a .cc-blockquote-border in the selection, in case the style was toggled off using the regular blockquote btn
                    if (!$borderElement.find('.cc-blockquote-border').length) {
                       $borderElement = $(' ').addClass('cc-blockquote-border');
                        $blockquote.children().last().append($borderElement);
                    }
                }

            } else {

                // First we find the parent
 element
                var $selectedElement = $(editor.selection.getNode());
                var $blockquote = $selectedElement.closest('.cc-blockquote');

                // Since the format is already applied, we remove the border element from inside the blockquote
                $blockquote.find('span.cc-blockquote-border').remove();

                // We also have to manually remove all classes that are not part of the formatter
                $blockquote.removeClass('cc-blockquote-text cc-blockquote-image cc-blockquote-' + alignment);

                // And then simply remove the format to get rid of the blockquote
                editor.formatter.remove('cc_blockquote_format');
            }

        };

        editor.on('init', function(e) {
            editor.formatter.register(
                'cc_blockquote_format', {
                    block: 'blockquote',
                    classes: ['cc-blockquote'],
                    //attributes: {'class': 'cc-blockquote-%value'},  // workaround for http://www.tinymce.com/develop/bugtracker_view.php?id=6472
                    wrapper: true
                }
            );
        });

        editor.addButton('BlockquoteLeft', {
            text: 'Blockquote Left',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('left');
                if (!this.active) { this.active(true); }
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('left', ctrl);
                });
            }
        });

        editor.addButton('BlockquoteCenter', {
            text: 'Blockquote Center',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('center');
                if (!this.active) { this.active(true); }
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('center', ctrl);
                });
            }
        });

        editor.addButton('BlockquoteRight', {
            text: 'Blockquote Right',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('right');
                if (!this.active) { this.active(true); }
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('right', ctrl);
                });
            }
        });

    });

})(jQuery);<commit_msg>Fix typo, add button state refresh after click<commit_after>(function($) {
    tinymce.PluginManager.add('blockquote', function(editor, url) {

        var checkFormatMatch = function(alignment, ctrl) {

            // Check if the selection matches the format
            var formatMatch = editor.formatter.match('cc_blockquote_format');

            // Some magic to find the blockquote element from inside the selection
            var $selectedElement = $(editor.selection.getNode());

            if ($selectedElement.is('blockquote')) {
                $blockquote = $selectedElement;
            } else {
                $blockquote = $selectedElement.closest('blockquote');
            }

            var alignmentMatch = $blockquote.hasClass('cc-blockquote-' + alignment);
            var borderElementMatch = $blockquote.find('.cc-blockquote-border').length;
            var imageElementMatch = $blockquote.hasClass('cc-blockquote-image');

            // If all conditions are true, the button should be in its active state
            ctrl.active( formatMatch && alignmentMatch && (borderElementMatch || imageElementMatch) );

        };

        var toggleBlockquoteFormat = function(alignment) {

            if (!editor.formatter.match('cc_blockquote_format')) {

                // If the blockquote format is not already applied to the element, we apply it before doing anything else.
                editor.formatter.apply('cc_blockquote_format');

                // Some magic to find the blockquote element from inside the selection
                var $selectedElement = $(editor.selection.getNode());

                if ($selectedElement.is('blockquote')) {
                    $blockquote = $selectedElement;
                } else {
                    $blockquote = $selectedElement.closest('blockquote');
                }

                $blockquote.addClass('cc-blockquote-' + alignment);

                var $img = $blockquote.find('img');
                if ($img.length) {
                    $blockquote.addClass('cc-blockquote-image');
                } else {
                    $blockquote.addClass('cc-blockquote-text');

                    // Check whether or not we already have a .cc-blockquote-border in the selection, in case the style was toggled off using the regular blockquote btn
                    if (!$blockquote.find('.cc-blockquote-border').length) {
                        $borderElement = $(' ').addClass('cc-blockquote-border');
                        $blockquote.children().last().append($borderElement);
                    }
                }

            } else {

                // First we find the parent
 element
                var $selectedElement = $(editor.selection.getNode());
                var $blockquote = $selectedElement.closest('.cc-blockquote');

                // Since the format is already applied, we remove the border element from inside the blockquote
                $blockquote.find('span.cc-blockquote-border').remove();

                // We also have to manually remove all classes that are not part of the formatter
                $blockquote.removeClass('cc-blockquote-text cc-blockquote-image cc-blockquote-' + alignment);

                // And then simply remove the format to get rid of the blockquote
                editor.formatter.remove('cc_blockquote_format');
            }

            editor.nodeChanged(); // refresh the button state

        };

        editor.on('init', function(e) {
            editor.formatter.register(
                'cc_blockquote_format', {
                    block: 'blockquote',
                    classes: ['cc-blockquote'],
                    //attributes: {'class': 'cc-blockquote-%value'},  // workaround for http://www.tinymce.com/develop/bugtracker_view.php?id=6472
                    wrapper: true
                }
            );
        });

        editor.addButton('BlockquoteLeft', {
            text: 'Blockquote Left',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('left');
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('left', ctrl);
                });
            }
        });

        editor.addButton('BlockquoteCenter', {
            text: 'Blockquote Center',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('center');
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('center', ctrl);
                });
            }
        });

        editor.addButton('BlockquoteRight', {
            text: 'Blockquote Right',
            icon: false,
            onclick: function() {
                toggleBlockquoteFormat('right');
            },
            onPostRender: function() {
                var ctrl = this;
                editor.on('NodeChange', function(e) {
                    checkFormatMatch('right', ctrl);
                });
            }
        });

    });

})(jQuery);<|endoftext|>


<commit_before>import * as constants from '../constants';

const initialState = {
};

function auth(state = initialState, action) {
    switch (action.type) {
        default:
            return state;
    }
}

export default auth;
<commit_msg>Update editReducer.js<commit_after>import * as constants from '../constants';

const initialState = {
    "object": {},
    "load": false,
    "error": null
};

function edit(state = initialState, action) {
    switch (action.type) {
        case constants.EDIT_FORM_GET_DATA_REQUEST:
            return Object.assign({}, state, {
                load: true,
                error: null
            });

        case constants.EDIT_FORM_GET_DATA_FALSE:
            return Object.assign({}, state, {
                load: false,
                error: action.error
            });

        case constants.EDIT_FORM_GET_DATA_DONE:
            return Object.assign({}, state, {
                object: action.data,
                load: false,
                error: null
            });

        default:
            return state;
    }
}

export default edit;
<|endoftext|>


<commit_before>const defaultState = {
  members: [],
  membersToShow: 6,
  current_member: {
    donations: [],
    comments: [],
    info: '',
    name: 'Charlie',
    goal: 0
  },
  select: {
    open: false,
    donationAmount: 10
  }
}

function helpSomeone(state = defaultState, action) {
  console.log("state: ", action.type);
	switch(action.type) {

		case 'SET_MEMBERS_COUNT':
			return {...state, membersToShow: action.membersToShow}

    case 'GET_MEMBERS_PENDING':
      return {...state, fetching: true}
  	case 'GET_MEMBERS_REJECTED':
      return {...state, fetching: true, error: action.payload}
  	case 'GET_MEMBERS_FULFILLED':
      return {...state, fetching: true, fetched: true, members: action.payload.data}

		case 'GET_FEATURED_MEMBERS_PENDING':
      return {...state, fetching: true}
  	case 'GET_FEATURED_MEMBERS_REJECTED':
      return {...state, fetching: true, error: action.payload}
  	case 'GET_FEATURED_MEMBERS_FULFILLED':
      return {...state, fetching: true, fetched: true, members: action.payload.data}

    case 'GET_SINGLE_MEMBER_PENDING':
      return {...state, fetching: true}
    case 'GET_SINGLE_MEMBER_REJECTED':
      return {...state, fetching: true, error: action.payload}
    case 'GET_SINGLE_MEMBER_FULFILLED':
      return {...state, fetching: true, fetched: true, current_member: action.payload.data}
    case 'SET_CURRENT_MEMBER':
      return {...state, current_member: action.member}

    default:
			return state
	}
}

export default helpSomeone;
<commit_msg>helpSomeone reducer: added sponsors to default state<commit_after>const defaultState = {
  members: [],
  membersToShow: 6,
  current_member: {
    sponsors: [],
    donations: [],
    comments: [],
    info: '',
    name: 'Charlie',
    goal: 0
  },
  select: {
    open: false,
    donationAmount: 10
  }
}

function helpSomeone(state = defaultState, action) {
  console.log("state: ", action.type);
	switch(action.type) {

		case 'SET_MEMBERS_COUNT':
			return {...state, membersToShow: action.membersToShow}

    case 'GET_MEMBERS_PENDING':
      return {...state, fetching: true}
  	case 'GET_MEMBERS_REJECTED':
      return {...state, fetching: true, error: action.payload}
  	case 'GET_MEMBERS_FULFILLED':
      return {...state, fetching: true, fetched: true, members: action.payload.data}

		case 'GET_FEATURED_MEMBERS_PENDING':
      return {...state, fetching: true}
  	case 'GET_FEATURED_MEMBERS_REJECTED':
      return {...state, fetching: true, error: action.payload}
  	case 'GET_FEATURED_MEMBERS_FULFILLED':
      return {...state, fetching: true, fetched: true, members: action.payload.data}

    case 'GET_SINGLE_MEMBER_PENDING':
      return {...state, fetching: true}
    case 'GET_SINGLE_MEMBER_REJECTED':
      return {...state, fetching: true, error: action.payload}
    case 'GET_SINGLE_MEMBER_FULFILLED':
      return {...state, fetching: true, fetched: true, current_member: action.payload.data}
    case 'SET_CURRENT_MEMBER':
      return {...state, current_member: action.member}

    default:
			return state
	}
}

export default helpSomeone;
<|endoftext|>


<commit_before>import Harness from '../../../test/harness';
import SelectBoxesComponent from './SelectBoxes';

import {
  comp1,
} from './fixtures';

describe('SelectBoxes Component', () => {
  it('Should build a SelectBoxes component', () => {
    return Harness.testCreate(SelectBoxesComponent, comp1).then((component) => {
      Harness.testElements(component, 'input[type="checkbox"]', 8);
    });
  });
});
<commit_msg>flesh out selectboxes min/max validation tests<commit_after>import assert from 'power-assert';
import Harness from '../../../test/harness';
import SelectBoxesComponent from './SelectBoxes';
import Formio from './../../Formio';

import {
  comp1
} from './fixtures';

describe('SelectBoxes Component', () => {
  it('Should build a SelectBoxes component', () => {
    return Harness.testCreate(SelectBoxesComponent, comp1).then((component) => {
      Harness.testElements(component, 'input[type="checkbox"]', 8);
    });
  });

  describe('error messages', () => {
    it('Should have a minSelectedCount validation message', () => {
      const formJson = {
        components: [
          {
            type: 'selectboxes',
            key: 'options',
            values: [
              { label: 'Option 1', value: '1' },
              { label: 'Option 2', value: '2' },
              { label: 'Option 3', value: '3' },
              { label: 'Option 4', value: '4' }
            ],
            validate: {
              minSelectedCount: 2
            }
          }
        ]
      };
      const element = document.createElement('div');
      return Formio.createForm(element, formJson)
        .then(async form => {
          form.submission = {
            data: {
              options: { 1: true }
            }
          };
          const comp = form.getComponent('options');
          setTimeout(() => {
            const { messageContainer } = comp.refs;
            assert.equal(
              messageContainer.textContent.trim(),
              'You must select at least 2 items.'
            );
          }, 300);
        });
    });

    it('Should use the minSelectedCountMessage if provided', () => {
      const formJson = {
        components: [
          {
            type: 'selectboxes',
            key: 'options',
            values: [
              { label: 'Option 1', value: '1' },
              { label: 'Option 2', value: '2' },
              { label: 'Option 3', value: '3' },
              { label: 'Option 4', value: '4' }
            ],
            validate: {
              minSelectedCount: 2,
            },
            minSelectedCountMessage: 'Please select at least {{minCount}} items.'
          }
        ]
      };
      const element = document.createElement('div');
      return Formio.createForm(element, formJson)
        .then(async form => {
          form.submission = {
            data: {
              options: { 1: true }
            }
          };
          const comp = form.getComponent('options');
          setTimeout(() => {
            const { messageContainer } = comp.refs;
            assert.equal(
              messageContainer.textContent.trim(),
              'Please select at least 2 items.'
            );
          }, 300);
        });
    });

    it('Should have a maxSelectedCount validation message', () => {
      const formJson = {
        components: [
          {
            type: 'selectboxes',
            key: 'options',
            values: [
              { label: 'Option 1', value: '1' },
              { label: 'Option 2', value: '2' },
              { label: 'Option 3', value: '3' },
              { label: 'Option 4', value: '4' }
            ],
            validate: {
              maxSelectedCount: 2
            }
          }
        ]
      };
      const element = document.createElement('div');
      return Formio.createForm(element, formJson)
        .then(async form => {
          form.submission = {
            data: {
              options: { 1: true, 2: true, 3: true }
            }
          };
          const comp = form.getComponent('options');
          setTimeout(() => {
            const { messageContainer } = comp.refs;
            assert.equal(
              messageContainer.textContent.trim(),
              'You can only select up to 2 items.'
            );
          }, 300);
        });
    });

    it('Should use the maxSelectedCountMessage if provided', () => {
      const formJson = {
        components: [
          {
            type: 'selectboxes',
            key: 'options',
            values: [
              { label: 'Option 1', value: '1' },
              { label: 'Option 2', value: '2' },
              { label: 'Option 3', value: '3' },
              { label: 'Option 4', value: '4' }
            ],
            validate: {
              maxSelectedCount: 2,
            },
            maxSelectedCountMessage: 'Please select {{maxCount}} items at most.'
          }
        ]
      };
      const element = document.createElement('div');
      return Formio.createForm(element, formJson)
        .then(async form => {
          form.submission = {
            data: {
              options: { 1: true, 2: true, 3: true }
            }
          };
          const comp = form.getComponent('options');
          setTimeout(() => {
            const { messageContainer } = comp.refs;
            assert.equal(
              messageContainer.textContent.trim(),
              'Please select 2 items at most.'
            );
          }, 300);
        });
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react'
import {
  StyleSheet,
  View,
  Text,
  ListView,
  TouchableOpacity,
} from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'

import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import style from '../../common/style'
import moment from 'moment'

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
    backgroundColor: style.colors.background,
  },
  constainer: {
    margin: 10,
  },
  sessionTitle: {
    color: style.colors.primary,
    fontSize: 16,
    fontWeight: '600',
  },
  textStyle: {
    color: style.colors.primary,
  },
  listItemWrapper: {
    padding: 10,
    flexDirection: 'row',
  },
  filterButtonWrapper: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  filterButton: {
    flexGrow: 2,
    backgroundColor: style.colors.backgroundColor,
    borderColor: style.colors.backgroundSecondary,
    borderWidth: 3,
  },
  filterButtonText: {
    alignSelf: 'center',
    paddingTop: 10,
    fontSize: 15,
    color: style.colors.color3,
  },
  formatPresentation: {
    color: style.colors.color1,
  },
  formatLightningTalk: {
    color: style.colors.color4,
  },
  formatWorkshop: {
    color: style.colors.color3,
  }
})

class SessionList extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      ds: ds.cloneWithRows(this.props.sessionsData)
    }
  }
  static propTypes = {
    sessionsData: PropTypes.array,
    selectedDay: PropTypes.string,
    day1: PropTypes.string,
    day2: PropTypes.string,
    filterSessionDay: PropTypes.func,
    navigation: PropTypes.object
  };

  formatArray(list) {
    return list.sort((a,b) => {
      return b.startTime > a.startTime ? -1
            :b.startTime > a.startTime ? 1
            :0
    })
  }
  filterSessionDay() {

  }

  filterSessions() {

  }
  getHeader() {
    return (
      <View style= { styles.filterButtonWrapper }>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one">
            <Text style={styles.filterButtonText}>DAY 1</Text></TouchableOpacity>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one"
        ><Text style={styles.filterButtonText}>DAY 2</Text></TouchableOpacity>
        <Icon.Button
          borderRadius={0}
          backgroundColor={style.colors.backgroundSecondary}
          name="filter"
          size={30}
          onPress={this.filterSessions()}>
        </Icon.Button>
      </View>
    )
  }

  getTimeSpan(fromTime, endTime) {
    let from = (moment(new Date(fromTime)).format('dddd, DD MMM HH:mm'))
    let end = (moment (new Date(endTime)).format('HH:mm'))
    return (from + ' - ' + end)
  }

  getSessionFormat(format) {
    if (format === 'presentation') {
      return <Text style={ styles.formatPresentation }>{format}</Text>
    } else if(format === 'lightning-talk') {
      return <Text style={ styles.formatLightningTalk }>{format}</Text>
    } else {
      return <Text style={ styles.formatWorkshop }>{format}</Text>
    }
  }

  componentWillReceiveProps(nextProps) {
    if(this.props.sessionsData !== nextProps.sessionsData) {
      this.setState(()=> {
        return {ds: this.state.ds.cloneWithRows(this.formatArray(nextProps.sessionsData))}
      });
    }
  }

  render() {
    if(!this.state.ds) return null
    return (
      <View style={ styles.listContainer }>
        {this.getHeader()}
        <ListView
          enableEmptySections={true} style={ styles.list }
          dataSource={ this.state.ds }
          renderRow={this.renderRow.bind(this)} />
      </View>
    )
  }

  renderRow(rowData, rowID) {
    return (
        <View style={ styles.listItemWrapper} key={ rowData.sessionId }>
          <View>
            <Icon name="star-o" style={{ paddingRight: 10 }}size={ 30 } color={style.colors.color4}/>
          </View>
          <TouchableOpacity style={{ paddingRight: 40 }} onPress={ () => this._onRowPressed(rowData) } key={ rowID }>
            <Text style={ styles.sessionTitle }>{rowData.title}</Text>
            { this.getSessionFormat(rowData.format) }
            <Text style={ styles.textStyle }>{ this.getTimeSpan(rowData.startTime, rowData.endTime) }</Text>
            <Text style={ styles.textStyle }>{ rowData.room }</Text>
          </TouchableOpacity>
        </View>
    )
  }

    _onRowPressed(rowData) {
      this.props.navigation.navigate('SessionDetail',
      { sessionData: rowData })
    }
  }

const mapStateToProps = (state) => {
  return {
    sessionsData: state.sessions
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    }
  }

<commit_msg>added day toggling to session list<commit_after>import React from 'react'
import {
  StyleSheet,
  View,
  Text,
  ListView,
  TouchableOpacity,
} from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'

import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import style from '../../common/style'
import moment from 'moment'

import { setSelectedDay } from '../../actions/conferenceDays'

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
    backgroundColor: style.colors.background,
  },
  constainer: {
    margin: 10,
  },
  sessionTitle: {
    color: style.colors.primary,
    fontSize: 16,
    fontWeight: '600',
  },
  textStyle: {
    color: style.colors.primary,
  },
  listItemWrapper: {
    padding: 10,
    flexDirection: 'row',
  },
  filterButtonWrapper: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  filterButton: {
    flexGrow: 2,
    backgroundColor: style.colors.backgroundColor,
    borderColor: style.colors.backgroundSecondary,
    borderWidth: 3,
  },
  filterButtonText: {
    alignSelf: 'center',
    paddingTop: 10,
    fontSize: 15,
    color: style.colors.color3,
  },
  formatPresentation: {
    color: style.colors.color1,
  },
  formatLightningTalk: {
    color: style.colors.color4,
  },
  formatWorkshop: {
    color: style.colors.color3,
  }
})

class SessionList extends React.Component {

  constructor(props) {
    super(props)
    const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
    this.state = {
      ds: ds.cloneWithRows(this.props.sessionsData)
    }
  }
  static propTypes = {
    sessionsData: PropTypes.array,
    selectedDay: PropTypes.string,
    day1: PropTypes.string,
    day2: PropTypes.string,
    filterSessionDay: PropTypes.func,
    navigation: PropTypes.object
  };

  filterSessions() {
  }
  getHeader() {
    return (
      <View style= { styles.filterButtonWrapper }>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={ ()=> this.props.filterSessionDay(this.props.day1) }
          title="Day 1"
          accessibilityLabel="List sessions, day one">
            <Text style={styles.filterButtonText}>DAY 1</Text></TouchableOpacity>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={() => this.props.filterSessionDay(this.props.day2)}
          title="Day 2"
          accessibilityLabel="List sessions, day one"
        ><Text style={styles.filterButtonText}>DAY 2</Text></TouchableOpacity>
        <Icon.Button
          borderRadius={0}
          backgroundColor={style.colors.backgroundSecondary}
          name="filter"
          size={30}
          onPress={this.filterSessions()}>
        </Icon.Button>
      </View>
    )
  }

  getTimeSpan(fromTime, endTime) {
    let from = (moment(new Date(fromTime)).format('dddd, DD MMM HH:mm'))
    let end = (moment (new Date(endTime)).format('HH:mm'))
    return (from + ' - ' + end)
  }

  getSessionFormat(format) {
    if (format === 'presentation') {
      return <Text style={ styles.formatPresentation }>{format}</Text>
    } else if(format === 'lightning-talk') {
      return <Text style={ styles.formatLightningTalk }>{format}</Text>
    } else {
      return <Text style={ styles.formatWorkshop }>{format}</Text>
    }
  }

  componentWillReceiveProps(nextProps) {
    if(this.props.sessionsData !== nextProps.sessionsData) {
      this.setState(()=> {
        return { ds: this.state.ds.cloneWithRows(nextProps.sessionsData) }
      })
    }
  }

  render() {
    if(!this.state.ds) return null
    return (
      <View style={ styles.listContainer }>
        {this.getHeader()}
        <ListView
          enableEmptySections={true} style={ styles.list }
          dataSource={ this.state.ds }
          renderRow={this.renderRow.bind(this)} />
      </View>
    )
  }

  renderRow(rowData, rowID) {
    return (
        <View style={ styles.listItemWrapper} key={ rowData.sessionId }>
          <View>
            <Icon name="star-o" style={{ paddingRight: 10 }}size={ 30 } color={style.colors.color4}/>
          </View>
          <TouchableOpacity style={{ paddingRight: 40 }} onPress={ () => this._onRowPressed(rowData) } key={ rowID }>
            <Text style={ styles.sessionTitle }>{rowData.title}</Text>
            { this.getSessionFormat(rowData.format) }
            <Text style={ styles.textStyle }>{ this.getTimeSpan(rowData.startTime, rowData.endTime) }</Text>
            <Text style={ styles.textStyle }>{ rowData.room }</Text>
          </TouchableOpacity>
        </View>
    )
  }

    _onRowPressed(rowData) {
      this.props.navigation.navigate('SessionDetail',
      { sessionData: rowData })
    }
  }

const mapStateToProps = (state) => {
  let fSessions = (state.sessions)
    .filter( ses => ses.startTime.slice(0,10) === state.conferenceDays.selectedDay)
    .sort((a,b) => {
      return b.startTime > a.startTime ? -1
            :b.startTime > a.startTime ? 1
            :0
    })

  return {
    sessionsData: fSessions,
    selectedDay: state.conferenceDays.selectedDay,
    day1: state.conferenceDays.days.day1,
    day2: state.conferenceDays.days.day2,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
      filterSessionDay: (day) => { dispatch(setSelectedDay(day)) }
    }
  }

export default connect(mapStateToProps, mapDispatchToProps)(SessionList)
<|endoftext|>


<commit_before>/*
 * touchmouse.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['../../config'], function(config){

  /**
   * handles mouse and touch events
   * dispatches tap and doubletap events
   * dispatches idle and active events
   * unifies mouse and touch events to touchmouse events
   * checks if click and tap events really are click events by tracking moved positions and time between last click
   *
   * @example
   * document.getElementById('slider').addEventListener('touchmousedown', down);
   * document.getElementById('slider').addEventListener('tap', tap);
   * document.getElementById('slider').addEventListener('doubletap', doubletap);
   * window.addEventListener('idle', idle);
   * window.addEventListener('active', active);
   *
   * @namespace touchmouse
   * @memberof dom.utils
   **/

  // event constants that should be dispatched
  var EVENTS = {
    DOWN: 'touchmousedown',
    UP: 'touchmouseup',
    MOVE: 'touchmousemove',
    TAP: 'tap',
    DOUBLE_TAP: 'doubletap',
    IDLE: 'idle',
    ACTIVE: 'active'
  };

  // tracked touches via pointer id
  var trackedTouches = [];

  // tracked positions (for pointer events only first finger gets tracked)
  var trackedPositions = {start: {}, end: {}, hadMultiple: false};

  // stored idle timeout id for dispatching idle event, gets resetted on every action
  var idleTimeout = null;

  // stores if was idle before when reactivating
  var wasIdle = false;

  /**
   * checks event type, tracks positions, converts to touchmouse event, etc.
   *
   * @method onMouseEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from mouse listener
   **/
  var onMouseEvent = function(event) {
    var type, evt;

    switch (event.type) {
      case 'mousedown': type = EVENTS.DOWN; break;
      case 'mouseup':   type = EVENTS.UP;   break;
      case 'mousemove': type = EVENTS.MOVE; break;
      default:
        return;
    }

    // create appropriate touchmouse event based on original event
    evt = _createEvent(type, event, event.pageX, event.pageY, -1);

    // dispatch touchmouse event on target
    event.target.dispatchEvent(evt);

    if (type === EVENTS.DOWN) {
      // store start mouse position
      trackedPositions.start = {x: evt.pageX, y: evt.pageY};
    } else if (type === EVENTS.UP) {
      // store end mouse position
      trackedPositions.end = {x: evt.pageX, y: evt.pageY};

      // check if it was a tap
      checkTap(event.target, event, evt.pageX, evt.pageY, -1);
    }

    // reset idle status
    _resetIdle();
  };

  /**
   * checks event type, tracks positions, converts to touchmouse event, etc.
   *
   * @method onTouchEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from mouse listener
   **/
  var onTouchEvent = function(event) {
    var type, i, _len, target, touch, touches, activeTouches, k, evt;

    switch (event.type) {
      case 'touchstart':  type = EVENTS.DOWN; break;
      case 'touchend':    type = EVENTS.UP;   break;
      case 'touchcancel': type = EVENTS.UP;   break;
      case 'touchmove':   type = EVENTS.MOVE; break;
      default:
        return;
    }

    // store changed touches
    touches = event.changedTouches;

    // loop through all events and create and dispatch event for each changed pointer id
    // NOTE: this is not so performant but easier to use in subclasses
    for (i = 0, _len = touches.length; i < _len; i++) {
      // store current touch
      touch = touches[i];

      // create appropriate event
      evt = _createEvent(type, event, touch.pageX, touch.pageY, touch.identifier);

      // if already has a tracked target for current pointerID
      // NOTE: maybe always used document.elementFromPoint as touch could move over multiple objects?
      if (trackedTouches[touch.identifier]) {
        // get cached, tracked target
        target = trackedTouches[touch.identifier].target;
      } else {
        // find target through document.elementFromPoint and cache target
        target = document.elementFromPoint(touch.pageX, touch.pageY);
        trackedTouches[touch.identifier] = {target: target};
      }

      // dispatch event on target
      target.dispatchEvent(evt);
    }

    // delete unused tracked touches
    if (type === EVENTS.UP) {
      var activeTouchIdentifiers = [];

      for (i = 0, _len = event.touches.length; i < _len; i++) {
        activeTouchIdentifiers.push(event.touches[i].identifier);
      }

      for (k in trackedTouches) {
        if (activeTouchIdentifiers.indexOf(k) === -1) {
          // TODO: IMPORTANT: dispatch mouse up for every deleted tracked touch that was not on changedTouches!
          delete trackedTouches[k];
        }
      }
    }

    // if finger count changed (via touchstart or touchend)
    if (type === EVENTS.DOWN || type === EVENTS.UP) {
      // calculate active touches
      activeTouches = 0;

      for (k in trackedTouches) {
        if (trackedTouches[k].target) {
          activeTouches++;
        }
      }
    }

    if (type === EVENTS.DOWN) {
      // store if had multiple fingers throughout transformations before ending a touch (i.e. before tapping)
      trackedPositions.hadMultiple = activeTouches > 1;

      // if it was the first finger
      if (activeTouches === 1) {
        // store start mouse position
        trackedPositions.start = {x: touches[0].pageX, y: touches[0].pageY};
      }
    } else if (type === EVENTS.UP) {
      // store end mouse position
      trackedPositions.end = {x: touches[0].pageX, y: touches[0].pageY};

      // check if it was a tap on last found target
      checkTap(target, event, touches[0].pageX, touches[0].pageY, touches[0].identifier);
    }

    // reset idle status
    _resetIdle();
  };

  /**
   * dispatches active event on window
   *
   * @method dispatchActive
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var dispatchActive = function(){
    wasIdle = false;

    var event = new Event(EVENTS.ACTIVE, {
      bubbles: true,
      cancelable: true
    });

    window.dispatchEvent(event);
  };

  /**
   * dispatches idle event on window
   *
   * @method dispatchIdle
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var dispatchIdle = function(){
    // check if was not idle before (prevent internal logic bug)
    if (wasIdle) return;
    wasIdle = true;

    var event = new Event(EVENTS.IDLE, {
      bubbles: true,
      cancelable: true
    });

    window.dispatchEvent(event);
  };

  /**
   * checks if event was a valid tap
   * i.e. if not moved too far and did not have multiple fingers
   * also checks for double taps
   *
   * @method checkTap
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {DomElement} target that was tapped and stores multiple tap counts
   **/
  var checkTap = function(target, originalEvent, x, y, pointerID){
    // allow a maximum of 20 pixels to let a click event be dispatched
    // and only one finger while pressing an object
    if (! (trackedPositions.hadMultiple ||
      Math.abs(trackedPositions.end.x - trackedPositions.start.x) > 20 ||
      Math.abs(trackedPositions.end.y - trackedPositions.start.y) > 20)
    ) {
      var evt = null;
      var wasDoubleTap = false;

      var now = Date.now();

      // if tapped again within 350 ms
      if (now - target._lastTapTime < 350) {
        // and is an even tap count
        if ((target._taps + 1) % 2 === 0) {
          // create double tap event
          wasDoubleTap = true;
        }
      } else {
        // otherwise reset taps
        target._taps = 0;
      }

      // store last tap time on dom element
      target._lastTapTime = now;

      // increment tap count on dom element
      target._taps++;

      // if it was a valid tap: dispatch event, pass tap count to event
      evt = _createEvent(EVENTS.TAP, originalEvent, x, y, pointerID);
      evt.taps = target._taps;
      target.dispatchEvent(evt);

      // if also was a double tap
      if (wasDoubleTap) {
        // dispatch a double tap event on target, pass tap count to event
        evt = _createEvent(EVENTS.DOUBLE_TAP, originalEvent, x, y, pointerID);
        evt.taps = target._taps;
        target.dispatchEvent(evt);
      }
    }
  };

  /**
   * checks moved distance and if had multiple fingers
   * prevents event if distance was too long
   *
   * @method onClickEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from click listener
   **/
  var onClickEvent = function(event){
    // allow a maximum of 20 pixels to let a click event be dispatched
    // and only one finger while pressing an object
    if (trackedPositions.hadMultiple ||
      Math.abs(trackedPositions.end.x - trackedPositions.start.x) > 20 ||
      Math.abs(trackedPositions.end.y - trackedPositions.start.y) > 20
    ) {
      event.stopPropagation();
    }
  };

  /**
   * creates a custom event and returns it
   *
   * @method _createEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {String} type of event that should be created
   * @param {Event} originalEvent (if available) to store for later usage
   * @param {Number} x position of event
   * @param {Number} y position of event
   * @param {Number} pointerID that was used (multiple fingers)
   * @returns {Event} the created event
   **/
  var _createEvent = function(type, originalEvent, x, y, pointerID) {
    var event = new Event(type, {
      bubbles: true,
      cancelable: true
    });

    event.originalEvent = originalEvent;
    event.pageX = x;
    event.pageY = y;
    event.pointerID = pointerID;

    return event;
  };

  /**
   * resets idle state and dispatches active on window if was idle before
   *
   * @method _resetIdle
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var _resetIdle = function(){
    // if was idle before: dispatch active event
    if (wasIdle) dispatchActive();

    // reset idle timeout (idle every 15 seconds)
    if (idleTimeout) clearTimeout(idleTimeout);
    idleTimeout = setTimeout(dispatchIdle, 15000);
  };

  // check if supports touch events
  if (config.isTouch) {
    // if it does: listen to appropriate touch events in whole window
    window.addEventListener('touchstart', onTouchEvent);
    window.addEventListener('touchmove', onTouchEvent);
    window.addEventListener('touchend', onTouchEvent);
    window.addEventListener('touchcancel', onTouchEvent);
  } else {
    // otherwise check mouse events and convert them
    window.addEventListener('mousedown', onMouseEvent);
    window.addEventListener('mouseup', onMouseEvent);
    window.addEventListener('mousemove', onMouseEvent);
  }

  // TODO: is buggy, maybe leave mouse events as they are and only check for tap events as they are used internally and not by libraries?
  // check for click events (if moved while trying to click: do not dispatch click)
  //document.addEventListener('click', onClickEvent, true);

  _resetIdle();

});
<commit_msg>allow overriding event trackings<commit_after>/*
 * touchmouse.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['../../config'], function(config){

  /**
   * handles mouse and touch events
   * dispatches tap and doubletap events
   * dispatches idle and active events
   * unifies mouse and touch events to touchmouse events
   * checks if click and tap events really are click events by tracking moved positions and time between last click
   *
   * @example
   * document.getElementById('slider').addEventListener('touchmousedown', down);
   * document.getElementById('slider').addEventListener('tap', tap);
   * document.getElementById('slider').addEventListener('doubletap', doubletap);
   * window.addEventListener('idle', idle);
   * window.addEventListener('active', active);
   *
   * @namespace touchmouse
   * @memberof dom.utils
   **/

  // event constants that should be dispatched
  var EVENTS = {
    DOWN: 'touchmousedown',
    UP: 'touchmouseup',
    MOVE: 'touchmousemove',
    TAP: 'tap',
    DOUBLE_TAP: 'doubletap',
    IDLE: 'idle',
    ACTIVE: 'active'
  };

  // tracked touches via pointer id
  var trackedTouches = [];

  // tracked positions (for pointer events only first finger gets tracked)
  var trackedPositions = {start: {}, end: {}, hadMultiple: false};

  // stored idle timeout id for dispatching idle event, gets resetted on every action
  var idleTimeout = null;

  // stores if was idle before when reactivating
  var wasIdle = false;

  /**
   * checks event type, tracks positions, converts to touchmouse event, etc.
   *
   * @method onMouseEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from mouse listener
   **/
  var onMouseEvent = function(event) {
    var type, evt;

    switch (event.type) {
      case 'mousedown': type = EVENTS.DOWN; break;
      case 'mouseup':   type = EVENTS.UP;   break;
      case 'mousemove': type = EVENTS.MOVE; break;
      default:
        return;
    }

    // create appropriate touchmouse event based on original event
    evt = _createEvent(type, event, event.pageX, event.pageY, -1);

    // dispatch touchmouse event on target
    event.target.dispatchEvent(evt);

    if (type === EVENTS.DOWN) {
      // store start mouse position
      trackedPositions.start = {x: evt.pageX, y: evt.pageY};
    } else if (type === EVENTS.UP) {
      // store end mouse position
      trackedPositions.end = {x: evt.pageX, y: evt.pageY};

      // check if it was a tap
      checkTap(event.target, event, evt.pageX, evt.pageY, -1);
    }

    // reset idle status
    _resetIdle();
  };

  /**
   * checks event type, tracks positions, converts to touchmouse event, etc.
   *
   * @method onTouchEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from mouse listener
   **/
  var onTouchEvent = function(event) {
    var type, i, _len, target, touch, touches, activeTouches, k, evt;

    switch (event.type) {
      case 'touchstart':  type = EVENTS.DOWN; break;
      case 'touchend':    type = EVENTS.UP;   break;
      case 'touchcancel': type = EVENTS.UP;   break;
      case 'touchmove':   type = EVENTS.MOVE; break;
      default:
        return;
    }

    // store changed touches
    touches = event.changedTouches;

    // loop through all events and create and dispatch event for each changed pointer id
    // NOTE: this is not so performant but easier to use in subclasses
    for (i = 0, _len = touches.length; i < _len; i++) {
      // store current touch
      touch = touches[i];

      // create appropriate event
      evt = _createEvent(type, event, touch.pageX, touch.pageY, touch.identifier);

      // if already has a tracked target for current pointerID
      // NOTE: maybe always used document.elementFromPoint as touch could move over multiple objects?
      if (trackedTouches[touch.identifier]) {
        // get cached, tracked target
        target = trackedTouches[touch.identifier].target;
      } else {
        // find target through document.elementFromPoint and cache target
        target = document.elementFromPoint(touch.pageX, touch.pageY);
        trackedTouches[touch.identifier] = {target: target};
      }

      // dispatch event on target
      target.dispatchEvent(evt);
    }

    // delete unused tracked touches
    if (type === EVENTS.UP) {
      var activeTouchIdentifiers = [];

      for (i = 0, _len = event.touches.length; i < _len; i++) {
        activeTouchIdentifiers.push(event.touches[i].identifier);
      }

      for (k in trackedTouches) {
        if (activeTouchIdentifiers.indexOf(k) === -1) {
          // !!!!! also important now for remote tracking!!
          // TODO: IMPORTANT: dispatch mouse up for every deleted tracked touch that was not on changedTouches!
          delete trackedTouches[k];
        }
      }
    }

    // if finger count changed (via touchstart or touchend)
    if (type === EVENTS.DOWN || type === EVENTS.UP) {
      // calculate active touches
      activeTouches = 0;

      for (k in trackedTouches) {
        if (trackedTouches[k].target) {
          activeTouches++;
        }
      }
    }

    if (type === EVENTS.DOWN) {
      // store if had multiple fingers throughout transformations before ending a touch (i.e. before tapping)
      trackedPositions.hadMultiple = activeTouches > 1;

      // if it was the first finger
      if (activeTouches === 1) {
        // store start mouse position
        trackedPositions.start = {x: touches[0].pageX, y: touches[0].pageY};
      }
    } else if (type === EVENTS.UP) {
      // store end mouse position
      trackedPositions.end = {x: touches[0].pageX, y: touches[0].pageY};

      // check if it was a tap on last found target
      checkTap(target, event, touches[0].pageX, touches[0].pageY, touches[0].identifier);
    }

    // reset idle status
    _resetIdle();
  };

  /**
   * dispatches active event on window
   *
   * @method dispatchActive
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var dispatchActive = function(){
    wasIdle = false;

    var event = new Event(EVENTS.ACTIVE, {
      bubbles: true,
      cancelable: true
    });

    window.dispatchEvent(event);
  };

  /**
   * dispatches idle event on window
   *
   * @method dispatchIdle
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var dispatchIdle = function(){
    // check if was not idle before (prevent internal logic bug)
    if (wasIdle) return;
    wasIdle = true;

    var event = new Event(EVENTS.IDLE, {
      bubbles: true,
      cancelable: true
    });

    window.dispatchEvent(event);
  };

  /**
   * checks if event was a valid tap
   * i.e. if not moved too far and did not have multiple fingers
   * also checks for double taps
   *
   * @method checkTap
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {DomElement} target that was tapped and stores multiple tap counts
   **/
  var checkTap = function(target, originalEvent, x, y, pointerID){
    // allow a maximum of 20 pixels to let a click event be dispatched
    // and only one finger while pressing an object
    if (! (trackedPositions.hadMultiple ||
      Math.abs(trackedPositions.end.x - trackedPositions.start.x) > 20 ||
      Math.abs(trackedPositions.end.y - trackedPositions.start.y) > 20)
    ) {
      var evt = null;
      var wasDoubleTap = false;

      var now = Date.now();

      // if tapped again within 350 ms
      if (now - target._lastTapTime < 350) {
        // and is an even tap count
        if ((target._taps + 1) % 2 === 0) {
          // create double tap event
          wasDoubleTap = true;
        }
      } else {
        // otherwise reset taps
        target._taps = 0;
      }

      // store last tap time on dom element
      target._lastTapTime = now;

      // increment tap count on dom element
      target._taps++;

      // if it was a valid tap: dispatch event, pass tap count to event
      evt = _createEvent(EVENTS.TAP, originalEvent, x, y, pointerID);
      evt.taps = target._taps;
      target.dispatchEvent(evt);

      // if also was a double tap
      if (wasDoubleTap) {
        // dispatch a double tap event on target, pass tap count to event
        evt = _createEvent(EVENTS.DOUBLE_TAP, originalEvent, x, y, pointerID);
        evt.taps = target._taps;
        target.dispatchEvent(evt);
      }
    }
  };

  /**
   * checks moved distance and if had multiple fingers
   * prevents event if distance was too long
   *
   * @method onClickEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {Event} event dispatched from click listener
   **/
  var onClickEvent = function(event){
    // allow a maximum of 20 pixels to let a click event be dispatched
    // and only one finger while pressing an object
    if (trackedPositions.hadMultiple ||
      Math.abs(trackedPositions.end.x - trackedPositions.start.x) > 20 ||
      Math.abs(trackedPositions.end.y - trackedPositions.start.y) > 20
    ) {
      event.stopPropagation();
    }
  };

  /**
   * creates a custom event and returns it
   *
   * @method _createEvent
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   * @param {String} type of event that should be created
   * @param {Event} originalEvent (if available) to store for later usage
   * @param {Number} x position of event
   * @param {Number} y position of event
   * @param {Number} pointerID that was used (multiple fingers)
   * @returns {Event} the created event
   **/
  var _createEvent = function(type, originalEvent, x, y, pointerID) {
    var event = new Event(type, {
      bubbles: true,
      cancelable: true
    });

    event.originalEvent = originalEvent;
    event.pageX = x;
    event.pageY = y;
    event.pointerID = pointerID;

    return event;
  };

  /**
   * resets idle state and dispatches active on window if was idle before
   *
   * @method _resetIdle
   * @memberof dom.utils.touchmouse
   * @instance
   * @private
   **/
  var _resetIdle = function(){
    // if was idle before: dispatch active event
    if (wasIdle) dispatchActive();

    // reset idle timeout (idle every 15 seconds)
    if (idleTimeout) clearTimeout(idleTimeout);
    idleTimeout = setTimeout(dispatchIdle, 15000);
  };

  // check if supports touch events
  if (config.isTouch || config.trackTouch) {
    // if it does: listen to appropriate touch events in whole window
    window.addEventListener('touchstart', onTouchEvent);
    window.addEventListener('touchmove', onTouchEvent);
    window.addEventListener('touchend', onTouchEvent);
    window.addEventListener('touchcancel', onTouchEvent);
  }

  if (! config.isTouch || config.trackMouse) {
    // otherwise check mouse events and convert them
    window.addEventListener('mousedown', onMouseEvent);
    window.addEventListener('mouseup', onMouseEvent);
    window.addEventListener('mousemove', onMouseEvent);
  }

  // TODO: is buggy, maybe leave mouse events as they are and only check for tap events as they are used internally and not by libraries?
  // check for click events (if moved while trying to click: do not dispatch click)
  //document.addEventListener('click', onClickEvent, true);

  _resetIdle();

});
<|endoftext|>


<commit_before>'use strict';

describe('On All songs page', function () {
    var scope, $httpBackend;

    var criteriaList = Ajabshahar.user.SongFilterConfig.filterCategories;

    var singerCategory = criteriaList[0];
    var wordCategory = criteriaList[2];

    var singerName = "Mooralala Marwada";
    var wordName = "Akath Katha";

    beforeEach(module("allSongsApp"));

    beforeEach(inject(function (_$controller_, _$rootScope_, _$window_, _songsContentService_, _songMapper_, _$httpBackend_, sortService) {
        scope = _$rootScope_.$new();
        $httpBackend = _$httpBackend_;

        _$controller_('allSongsController', {
            $scope: scope,
            $window: _$window_,
            $rootScope: _$rootScope_,
            songsContentService: _songsContentService_,
            songMapper: _songMapper_,
            sortService: sortService
        });

        var resetCategories = function (category) {
            category.value = "";
            category.disabled = undefined;
            category.active = undefined;
        };
        $httpBackend.when("GET", "/api/songs/getPublishedSongs").respond(test_AllSongsList);
        resetCategories(singerCategory);
        resetCategories(wordCategory);
    }));

    describe('When its initialized', function () {
        it(' should setup filter categories and mark all the categories that have 0 items to filter on', function () {
            /*$httpBackend.when("GET", "/api/words?showOnWordsLandingPage=true").respond({"words" : [{'id': 1, 'isRootWord': true}, {'id': 2, 'isRootWord': true}]});

             scope.init();
             $httpBackend.flush();

             expect(scope.words.length).toBe(2);*/
        });

    });

    describe('When user interacts with filter pardas then', function () {

        it('clicking a category in filter parda 1, should mark the category as currently active if it not already selected and mark other categories as active=false', function () {
            $httpBackend.flush();

            scope.filterCategoryClicked(singerCategory);
            expect(singerCategory.active).toBeTruthy();

            scope.filterCategoryClicked(wordCategory);
            expect(wordCategory.active).toBeTruthy();
            expect(singerCategory.active).toBeFalsy();

        });

        it('selecting a singer(or any other category) should mark the category as disabled', function () {

            $httpBackend.flush();

            scope.filterCategoryClicked(singerCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(singerName);

            expect(singerCategory.active).toBeFalsy();
            expect(singerCategory.disabled).toBeTruthy();
            expect(scope.openSecondParda).toBeFalsy();

        });

        it('selecting a singer(or any other category) and a word(or any other category) should mark both the categories as disabled', function () {
            $httpBackend.flush();

            //select a singer
            scope.filterCategoryClicked(singerCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(singerName);
            expect(singerCategory.active).toBeFalsy();
            expect(scope.openSecondParda).toBeFalsy();

            //select a word
            scope.filterCategoryClicked(wordCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(wordName);
            expect(wordCategory.active).toBeFalsy();
            expect(scope.openSecondParda).toBeFalsy();

            //check if both are disabled
            expect(singerCategory.disabled).toBeTruthy();
            expect(wordCategory.disabled).toBeTruthy();
        });
        it('deselecting a selected singer(or any other category) should mark the category as disabled=false', function () {
            $httpBackend.flush();

            //select a singer and a word
            scope.filterCategoryClicked(singerCategory);
            scope.filterItemSelected(singerName);

            scope.filterCategoryClicked(wordCategory);
            scope.filterItemSelected(wordName);

            //check if both are disabled
            expect(singerCategory.disabled).toBeTruthy();
            expect(wordCategory.disabled).toBeTruthy();

            //remove the word filter
            scope.removeFilterCriteria(wordCategory);

            //check if enabled
            expect(wordCategory.disabled).toBeFalsy();

        });
        it('selecting a singer(or any other category) should mark all other categories that dont have any values to filter on as disabled', function () {

        });
        it('clicking on reset all should remove all the filters from the filter parda 1 and display all songs', function () {

        })

    })
});

var test_AllSongsList = {
    songs: [
        {
            "id": 11,
            "umbrellaTitle": {
                "id": 38,
                "originalTitle": "भजन रो गुड़क रहयो गाड़ो",
                "englishTransliteration": "Bhajan Ro Gudak Rahyo Gaado",
                "englishTranslation": "The Cart of Meditation is Tottering"
            },
            "songTitle": {
                "id": 50,
                "originalTitle": "नित खैर मंगा",
                "englishTransliteration": "Nit Khair Manga",
                "englishTranslation": "Everyday I Ask For Your Well Being"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "",
            "soundCloudTrackId": "https://soundcloud.com/kashanilyas/we-main-chori-chori-tere-nal/sets",
            "thumbnailUrl": "https://farm8.staticflickr.com/7583/16097980187_72dfa07068_o.png",
            "duration": "07:45",
            "singers": [
                {
                    "id": 25,
                    "name": "Mukhtiyar Ali",
                    "hindiName": ""
                },
                {
                    "id": 10,
                    "name": "Mooralala Marwada",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 26,
                    "name": "Badar Muneer",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 1,
                        "wordOriginal": "अकथ कथा",
                        "wordTranslation": "Akath Katha",
                        "wordTransliteration": "Untellable Tale",
                        "hindiIntroExcerpt": "Akath means inexpressible, indescribable, ineffable. Beyond words. Beyond language. Beyond mind. But why all the hue and cry if the tale cannot be told?",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 14,
                        "wordOriginal": "Guitar",
                        "wordTranslation": "Guitar",
                        "wordTransliteration": "Gitara",
                        "hindiIntroExcerpt": "This is english intro excerpt",
                        "englishIntroExcerpt": "This is hindi intro excerpt",
                        "writers": [
                            {
                                "id": 7,
                                "name": "Parvathy Baul",
                                "hindiName": ""
                            }
                        ],
                        "rootWord": true
                    }
                ]
            }
        },
        {
            "id": 12,
            "umbrellaTitle": {
                "id": 57,
                "originalTitle": "थारा रंगमहल में",
                "englishTransliteration": "Thaara Rangmahal Mein",
                "englishTranslation": "In Your Colourful Palace"
            },
            "songTitle": {
                "id": 56,
                "originalTitle": "थारा रंगमहल में",
                "englishTransliteration": "Thaara Rangmahal Mein",
                "englishTranslation": "In Your Colourful Palace"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "sop02Ivqji0",
            "soundCloudTrackId": "https://soundcloud.com/kashanilyas/we-main-chori-chori-tere-nal/sets",
            "thumbnailUrl": "https://farm8.staticflickr.com/7511/16096481990_31028b8c63_o.jpg",
            "duration": "06:45",
            "singers": [
                {
                    "id": 12,
                    "name": "Prahlad Singh Tipanya",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 11,
                    "name": "Kabir",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": []
            }
        },
        {
            "id": 5,
            "umbrellaTitle": {
                "id": 36,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "songTitle": {
                "id": 35,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "J4IU5tDlD_s",
            "soundCloudTrackId": "174024475",
            "thumbnailUrl": "https://farm8.staticflickr.com/7513/16257916986_baf4e108cb_o.jpg",
            "duration": "07:05",
            "singers": [
                {
                    "id": 10,
                    "name": "Mooralala Marwada",
                    "hindiName": ""
                },
                {
                    "id": 21,
                    "name": "Sumar Kadu Jat",
                    "hindiName": ""
                },
                {
                    "id": 27,
                    "name": "Parvathy Baul",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 24,
                    "name": "Shah Abdul Latif Bhitai",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 5,
                        "wordOriginal": "जागना सोना",
                        "wordTranslation": "Waking Sleeping",
                        "wordTransliteration": "Jaagna Sona",
                        "hindiIntroExcerpt": "A story tells of how, when the Buddha attained illumination, somebody asked him: ‘So, are you enlightened now?’ The Buddha’s response was: ‘I am awake.’",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": true
                    }
                ]
            }
        },
        {
            "id": 8,
            "umbrellaTitle": {
                "id": 36,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "songTitle": {
                "id": 35,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "J4IU5tDlD_s",
            "soundCloudTrackId": "174024475",
            "thumbnailUrl": "https://farm9.staticflickr.com/8632/15663990083_cac15c4210_o.jpg",
            "duration": "04.55",
            "singers": [
                {
                    "id": 32,
                    "name": "Kaluram Bamaniya",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 11,
                    "name": "Kabir",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 1,
                        "wordOriginal": "अकथ कथा",
                        "wordTranslation": "Akath Katha",
                        "wordTransliteration": "Untellable Tale",
                        "hindiIntroExcerpt": "Akath means inexpressible, indescribable, ineffable. Beyond words. Beyond language. Beyond mind. But why all the hue and cry if the tale cannot be told?",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 5,
                        "wordOriginal": "जागना सोना",
                        "wordTranslation": "Waking Sleeping",
                        "wordTransliteration": "Jaagna Sona",
                        "hindiIntroExcerpt": "A story tells of how, when the Buddha attained illumination, somebody asked him: ‘So, are you enlightened now?’ The Buddha’s response was: ‘I am awake.’",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 10,
                        "wordOriginal": "bazaar",
                        "wordTranslation": "market",
                        "wordTransliteration": "bazaar",
                        "hindiIntroExcerpt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                        "englishIntroExcerpt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                        "writers": [
                            {
                                "id": 7,
                                "name": "Parvathy Baul",
                                "hindiName": ""
                            }
                        ],
                        "rootWord": false
                    }
                ]
            }
        }
    ]
};
<commit_msg>correcting a test for allSongsPage | indraneel<commit_after>'use strict';

describe('On All songs page', function() {
    var scope, $httpBackend;

    var criteriaList = Ajabshahar.user.SongFilterConfig.filterCategories;

    var singerCategory = criteriaList[0];
    var wordCategory = criteriaList[2];

    var singerName = "Mooralala Marwada";
    var wordName = "Akath Katha";

    beforeEach(module("allSongsApp"));

    beforeEach(inject(function (_$controller_, _$rootScope_, _$window_, _songsContentService_, _songMapper_, _$httpBackend_, sortService) {
        scope = _$rootScope_.$new();
        $httpBackend = _$httpBackend_;

        _$controller_('allSongsController', {
            $scope: scope,
            $window: _$window_,
            $rootScope: _$rootScope_,
            songsContentService: _songsContentService_,
            songMapper:_songMapper_,
            sortService:sortService
        });

        var resetCategories = function(category){
            category.value = "";
            category.disabled = undefined;
            category.active = undefined;
        };
        $httpBackend.when("GET", "/api/songs/getPublishedSongs").respond(test_AllSongsList);
        resetCategories(singerCategory);
        resetCategories(wordCategory);
    }));

    describe('When its initialized', function() {
        iit(' should setup filter categories and mark all the categories that have 0 items to filter on as disabled', function() {
            $httpBackend.flush();

            expect(scope.filterItems.Word.length).toBe(4);
            expect(scope.filterItems.Singer.length).toBe(6);
            expect(scope.filterItems.Poet.length).toBe(3);
            expect(scope.filterItems.Gathering.length).toBe(0);
        });

    });

    describe('When user interacts with filter pardas then', function () {

        it('clicking a category in filter parda 1, should mark the category as currently active if it not already selected and mark other categories as active=false', function () {
            $httpBackend.flush();

            scope.filterCategoryClicked(singerCategory);
            expect(singerCategory.active).toBeTruthy();

            scope.filterCategoryClicked(wordCategory);
            expect(wordCategory.active).toBeTruthy();
            expect(singerCategory.active).toBeFalsy();

        });

        it('selecting a singer(or any other category) should mark the category as disabled', function () {

            $httpBackend.flush();

            scope.filterCategoryClicked(singerCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(singerName);

            expect(singerCategory.active).toBeFalsy();
            expect(singerCategory.disabled).toBeTruthy();
            expect(scope.openSecondParda).toBeFalsy();

        });

        it('selecting a singer(or any other category) and a word(or any other category) should mark both the categories as disabled', function () {
            $httpBackend.flush();

            //select a singer
            scope.filterCategoryClicked(singerCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(singerName);
            expect(singerCategory.active).toBeFalsy();
            expect(scope.openSecondParda).toBeFalsy();

            //select a word
            scope.filterCategoryClicked(wordCategory);
            expect(scope.openSecondParda).toBeTruthy();
            scope.filterItemSelected(wordName);
            expect(wordCategory.active).toBeFalsy();
            expect(scope.openSecondParda).toBeFalsy();

            //check if both are disabled
            expect(singerCategory.disabled).toBeTruthy();
            expect(wordCategory.disabled).toBeTruthy();
        });
        it('deselecting a selected singer(or any other category) should mark the category as disabled=false', function () {
            $httpBackend.flush();

            //select a singer and a word
            scope.filterCategoryClicked(singerCategory);
            scope.filterItemSelected(singerName);

            scope.filterCategoryClicked(wordCategory);
            scope.filterItemSelected(wordName);

            //check if both are disabled
            expect(singerCategory.disabled).toBeTruthy();
            expect(wordCategory.disabled).toBeTruthy();

            //remove the word filter
            scope.removeFilterCriteria(wordCategory);

            //check if enabled
            expect(wordCategory.disabled).toBeFalsy();

        });
        it('selecting a singer(or any other category) should mark all other categories that dont have any values to filter on as disabled', function () {

        });
        it('clicking on reset all should remove all the filters from the filter parda 1 and display all songs', function () {

        })

    })
});

var test_AllSongsList = {
    songs: [
        {
            "id": 11,
            "umbrellaTitle": {
                "id": 38,
                "originalTitle": "भजन रो गुड़क रहयो गाड़ो",
                "englishTransliteration": "Bhajan Ro Gudak Rahyo Gaado",
                "englishTranslation": "The Cart of Meditation is Tottering"
            },
            "songTitle": {
                "id": 50,
                "originalTitle": "नित खैर मंगा",
                "englishTransliteration": "Nit Khair Manga",
                "englishTranslation": "Everyday I Ask For Your Well Being"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "",
            "soundCloudTrackId": "https://soundcloud.com/kashanilyas/we-main-chori-chori-tere-nal/sets",
            "thumbnailUrl": "https://farm8.staticflickr.com/7583/16097980187_72dfa07068_o.png",
            "duration": "07:45",
            "singers": [
                {
                    "id": 25,
                    "name": "Mukhtiyar Ali",
                    "hindiName": ""
                },
                {
                    "id": 10,
                    "name": "Mooralala Marwada",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 26,
                    "name": "Badar Muneer",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 1,
                        "wordOriginal": "अकथ कथा",
                        "wordTranslation": "Akath Katha",
                        "wordTransliteration": "Untellable Tale",
                        "hindiIntroExcerpt": "Akath means inexpressible, indescribable, ineffable. Beyond words. Beyond language. Beyond mind. But why all the hue and cry if the tale cannot be told?",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 14,
                        "wordOriginal": "Guitar",
                        "wordTranslation": "Guitar",
                        "wordTransliteration": "Gitara",
                        "hindiIntroExcerpt": "This is english intro excerpt",
                        "englishIntroExcerpt": "This is hindi intro excerpt",
                        "writers": [
                            {
                                "id": 7,
                                "name": "Parvathy Baul",
                                "hindiName": ""
                            }
                        ],
                        "rootWord": true
                    }
                ]
            }
        },
        {
            "id": 12,
            "umbrellaTitle": {
                "id": 57,
                "originalTitle": "थारा रंगमहल में",
                "englishTransliteration": "Thaara Rangmahal Mein",
                "englishTranslation": "In Your Colourful Palace"
            },
            "songTitle": {
                "id": 56,
                "originalTitle": "थारा रंगमहल में",
                "englishTransliteration": "Thaara Rangmahal Mein",
                "englishTranslation": "In Your Colourful Palace"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "sop02Ivqji0",
            "soundCloudTrackId": "https://soundcloud.com/kashanilyas/we-main-chori-chori-tere-nal/sets",
            "thumbnailUrl": "https://farm8.staticflickr.com/7511/16096481990_31028b8c63_o.jpg",
            "duration": "06:45",
            "singers": [
                {
                    "id": 12,
                    "name": "Prahlad Singh Tipanya",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 11,
                    "name": "Kabir",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": []
            }
        },
        {
            "id": 5,
            "umbrellaTitle": {
                "id": 36,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "songTitle": {
                "id": 35,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "J4IU5tDlD_s",
            "soundCloudTrackId": "174024475",
            "thumbnailUrl": "https://farm8.staticflickr.com/7513/16257916986_baf4e108cb_o.jpg",
            "duration": "07:05",
            "singers": [
                {
                    "id": 10,
                    "name": "Mooralala Marwada",
                    "hindiName": ""
                },
                {
                    "id": 21,
                    "name": "Sumar Kadu Jat",
                    "hindiName": ""
                },
                {
                    "id": 27,
                    "name": "Parvathy Baul",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 24,
                    "name": "Shah Abdul Latif Bhitai",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 5,
                        "wordOriginal": "जागना सोना",
                        "wordTranslation": "Waking Sleeping",
                        "wordTransliteration": "Jaagna Sona",
                        "hindiIntroExcerpt": "A story tells of how, when the Buddha attained illumination, somebody asked him: ‘So, are you enlightened now?’ The Buddha’s response was: ‘I am awake.’",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": true
                    }
                ]
            }
        },
        {
            "id": 8,
            "umbrellaTitle": {
                "id": 36,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "songTitle": {
                "id": 35,
                "originalTitle": "हिए काया में",
                "englishTransliteration": "Hiye Kaaya Mein",
                "englishTranslation": "In This Body"
            },
            "publish": true,
            "category": "Songs",
            "featured": true,
            "youTubeVideoId": "J4IU5tDlD_s",
            "soundCloudTrackId": "174024475",
            "thumbnailUrl": "https://farm9.staticflickr.com/8632/15663990083_cac15c4210_o.jpg",
            "duration": "04.55",
            "singers": [
                {
                    "id": 32,
                    "name": "Kaluram Bamaniya",
                    "hindiName": ""
                }
            ],
            "poet": [
                {
                    "id": 11,
                    "name": "Kabir",
                    "hindiName": ""
                }
            ],
            "words": {
                "words": [
                    {
                        "id": 1,
                        "wordOriginal": "अकथ कथा",
                        "wordTranslation": "Akath Katha",
                        "wordTransliteration": "Untellable Tale",
                        "hindiIntroExcerpt": "Akath means inexpressible, indescribable, ineffable. Beyond words. Beyond language. Beyond mind. But why all the hue and cry if the tale cannot be told?",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 5,
                        "wordOriginal": "जागना सोना",
                        "wordTranslation": "Waking Sleeping",
                        "wordTransliteration": "Jaagna Sona",
                        "hindiIntroExcerpt": "A story tells of how, when the Buddha attained illumination, somebody asked him: ‘So, are you enlightened now?’ The Buddha’s response was: ‘I am awake.’",
                        "englishIntroExcerpt": "",
                        "writers": [],
                        "rootWord": false
                    },
                    {
                        "id": 10,
                        "wordOriginal": "bazaar",
                        "wordTranslation": "market",
                        "wordTransliteration": "bazaar",
                        "hindiIntroExcerpt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                        "englishIntroExcerpt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                        "writers": [
                            {
                                "id": 7,
                                "name": "Parvathy Baul",
                                "hindiName": ""
                            }
                        ],
                        "rootWord": false
                    }
                ]
            }
        }
    ]
};
<|endoftext|>