<commit_before>/* global moment */
import Ember from 'ember';
import {
  module,
  test
} from 'qunit';
import startApp from 'ilios/tests/helpers/start-app';

var application;
var fixtures = {};

module('Acceptance: Dashboard Calendar', {
  beforeEach: function() {
    application = startApp();
    server.create('user', {id: 4136});
    let today = moment().hour(8);
    fixtures.today = today;
    server.create('userevent', {
      name: 'session 0',
      startDate: today.format(),
      endDate: today.clone().add(1, 'hour').format()
    });
  },

  afterEach: function() {
    Ember.run(application, 'destroy');
  }
});

test('load calendar', function(assert) {
  visit('/');
  andThen(function() {
    assert.equal(currentPath(), 'dashboard.index');
    let events = find('div.event');
    assert.equal(events.length, 1);
    assert.equal(getElementText(events), getText(fixtures.today.format('h:mma') + '-' + fixtures.today.clone().add(1, 'hour').format('h:mma') + ': session 0'));
  });
});
<commit_msg>Add basic calendar display tests<commit_after>/* global moment */
import Ember from 'ember';
import {
  module,
  test
} from 'qunit';
import startApp from 'ilios/tests/helpers/start-app';

var application;
var fixtures = {};

module('Acceptance: Dashboard Calendar', {
  beforeEach: function() {
    application = startApp();
    server.create('user', {id: 4136});
    fixtures.today = moment().hour(8);
    fixtures.startOfMonth = fixtures.today.clone().startOf('month');
    fixtures.endOfMonth = fixtures.today.clone().endOf('month').hour(22).minute(59);
    fixtures.startOfWeek = fixtures.today.clone().startOf('week');
    fixtures.endOfWeek = fixtures.today.clone().endOf('week').hour(22).minute(59);
    console.log(fixtures);
    server.create('userevent', {
      name: 'start of month',
      startDate: fixtures.startOfMonth.format(),
      endDate: fixtures.startOfMonth.clone().add(1, 'hour').format()
    });
    server.create('userevent', {
      name: 'start of week',
      startDate: fixtures.startOfWeek.format(),
      endDate: fixtures.startOfWeek.clone().add(1, 'hour').format()
    });
    server.create('userevent', {
      name: 'today',
      startDate: fixtures.today.format(),
      endDate: fixtures.today.clone().add(1, 'hour').format()
    });
    server.create('userevent', {
      name: 'end of week',
      startDate: fixtures.endOfWeek.format(),
      endDate: fixtures.endOfWeek.clone().add(1, 'hour').format()
    });
    server.create('userevent', {
      name: 'end of month',
      startDate: fixtures.endOfMonth.format(),
      endDate: fixtures.endOfMonth.clone().add(1, 'hour').format()
    });
  },

  afterEach: function() {
    Ember.run(application, 'destroy');
  }
});

test('load month calendar', function(assert) {
  visit('/dashboard?view=month');
  andThen(function() {
    assert.equal(currentPath(), 'dashboard.index');
    let events = find('div.event');
    assert.equal(events.length, 5);
    let eventInfo = '';
    eventInfo += fixtures.startOfMonth.format('h:mma') + '-' + fixtures.startOfMonth.clone().add(1, 'hour').format('h:mma') + ': start of month';
    eventInfo += fixtures.startOfWeek.format('h:mma') + '-' + fixtures.startOfWeek.clone().add(1, 'hour').format('h:mma') + ': start of week';
    eventInfo += fixtures.today.format('h:mma') + '-' + fixtures.today.clone().add(1, 'hour').format('h:mma') + ': today';
    eventInfo += fixtures.endOfWeek.format('h:mma') + '-' + fixtures.endOfWeek.clone().add(1, 'hour').format('h:mma') + ': end of week';
    eventInfo += fixtures.endOfMonth.format('h:mma') + '-' + fixtures.endOfMonth.clone().add(1, 'hour').format('h:mma') + ': end of month';
    assert.equal(getElementText(events), getText(eventInfo));

  });
});

test('load week calendar', function(assert) {
  visit('/');
  andThen(function() {
    assert.equal(currentPath(), 'dashboard.index');
    let events = find('div.event');
    assert.equal(events.length, 3);
    let eventInfo = '';
    eventInfo += fixtures.startOfWeek.format('h:mma') + '-' + fixtures.startOfWeek.clone().add(1, 'hour').format('h:mma') + ': start of week';
    eventInfo += fixtures.today.format('h:mma') + '-' + fixtures.today.clone().add(1, 'hour').format('h:mma') + ': today';
    eventInfo += fixtures.endOfWeek.format('h:mma') + '-' + fixtures.endOfWeek.clone().add(1, 'hour').format('h:mma') + ': end of week';
    assert.equal(getElementText(events), getText(eventInfo));

  });
});

test('load day calendar', function(assert) {
  visit('/dashboard?view=day');
  andThen(function() {
    assert.equal(currentPath(), 'dashboard.index');
    let events = find('div.event');
    assert.equal(events.length, 1);
    let eventInfo = '';
    eventInfo += fixtures.today.format('h:mma') + '-' + fixtures.today.clone().add(1, 'hour').format('h:mma') + ': today';
    assert.equal(getElementText(events), getText(eventInfo));

  });
});

test('click month day number and go to day', function(assert) {
  visit('/dashboard?view=month');
  andThen(function() {
    let dayOfMonth = fixtures.today.date();
    click(find('.day a').eq(dayOfMonth)).then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.format('YYYY-MM-DD') + '&view=day');
    });
  });
});

test('click week day title and go to day', function(assert) {
  visit('/dashboard?view=week');
  andThen(function() {
    let dayOfWeek = fixtures.today.day();
    click(find('.week-titles a').eq(dayOfWeek)).then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.format('YYYY-MM-DD') + '&view=day');
    });
  });
});

test('click forward on a day goes to next day', function(assert) {
  visit('/dashboard?view=day');
  andThen(function() {
    click('.calendar-time-picker li:eq(2)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.add(1, 'day').format('YYYY-MM-DD') + '&view=day');
    });
  });
});

test('click forward on a week goes to next week', function(assert) {
  visit('/dashboard?view=week');
  andThen(function() {
    click('.calendar-time-picker li:eq(2)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.add(1, 'week').format('YYYY-MM-DD'));
    });
  });
});

test('click forward on a month goes to next month', function(assert) {
  visit('/dashboard?view=month');
  andThen(function() {
    click('.calendar-time-picker li:eq(2)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.add(1, 'month').format('YYYY-MM-DD') + '&view=month');
    });
  });
});

test('click back on a day goes to previous day', function(assert) {
  visit('/dashboard?view=day');
  andThen(function() {
    click('.calendar-time-picker li:eq(0)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.subtract(1, 'day').format('YYYY-MM-DD') + '&view=day');
    });
  });
});

test('click back on a week goes to previous week', function(assert) {
  visit('/dashboard?view=week');
  andThen(function() {
    click('.calendar-time-picker li:eq(0)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.subtract(1, 'week').format('YYYY-MM-DD'));
    });
  });
});

test('click back on a month goes to previous month', function(assert) {
  visit('/dashboard?view=month');
  andThen(function() {
    click('.calendar-time-picker li:eq(0)').then(()=>{
      assert.equal(currentURL(), '/dashboard?date=' + fixtures.today.subtract(1, 'month').format('YYYY-MM-DD') + '&view=month');
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Element with named flags that can be added, removed, listed and checked.
 *
 * A flag, when set, adds a CSS class on the `$element` by combining `oo-ui-flaggedElement-` with
 * the flag name. Flags are primarily useful for styling.
 *
 * @abstract
 * @class
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {string|string[]} [flags] Flags describing importance and functionality, e.g. 'primary',
 *   'safe', 'progressive', 'destructive' or 'constructive'
 * @cfg {jQuery} [$flagged] Flagged node, assigned to #$flagged, omit to use #$element
 */
OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) {
	// Configuration initialization
	config = config || {};

	// Properties
	this.flags = {};
	this.$flagged = null;

	// Initialization
	this.setFlags( config.flags );
	this.setFlaggedElement( config.$flagged || this.$element );
};

/* Events */

/**
 * @event flag
 * @param {Object.<string,boolean>} changes Object keyed by flag name containing boolean
 *   added/removed properties
 */

/* Methods */

/**
 * Set the flagged element.
 *
 * If an element is already set, it will be cleaned up before setting up the new element.
 *
 * @param {jQuery} $flagged Element to add flags to
 */
OO.ui.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) {
	var classNames = Object.keys( this.flags ).map( function ( flag ) {
		return 'oo-ui-flaggedElement-' + flag;
	} ).join( ' ' );

	if ( this.$flagged ) {
		this.$flagged.removeClass( classNames );
	}

	this.$flagged = $flagged.addClass( classNames );
};

/**
 * Check if a flag is set.
 *
 * @param {string} flag Name of flag
 * @return {boolean} Has flag
 */
OO.ui.FlaggedElement.prototype.hasFlag = function ( flag ) {
	return flag in this.flags;
};

/**
 * Get the names of all flags set.
 *
 * @return {string[]} Flag names
 */
OO.ui.FlaggedElement.prototype.getFlags = function () {
	return Object.keys( this.flags );
};

/**
 * Clear all flags.
 *
 * @chainable
 * @fires flag
 */
OO.ui.FlaggedElement.prototype.clearFlags = function () {
	var flag, className,
		changes = {},
		remove = [],
		classPrefix = 'oo-ui-flaggedElement-';

	for ( flag in this.flags ) {
		className = classPrefix + flag;
		changes[ flag ] = false;
		delete this.flags[ flag ];
		remove.push( className );
	}

	if ( this.$flagged ) {
		this.$flagged.removeClass( remove.join( ' ' ) );
	}

	this.updateThemeClasses();
	this.emit( 'flag', changes );

	return this;
};

/**
 * Add one or more flags.
 *
 * @param {string|string[]|Object.<string, boolean>} flags One or more flags to add, or an object
 *  keyed by flag name containing boolean set/remove instructions.
 * @chainable
 * @fires flag
 */
OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) {
	var i, len, flag, className,
		changes = {},
		add = [],
		remove = [],
		classPrefix = 'oo-ui-flaggedElement-';

	if ( typeof flags === 'string' ) {
		className = classPrefix + flags;
		// Set
		if ( !this.flags[ flags ] ) {
			this.flags[ flags ] = true;
			add.push( className );
		}
	} else if ( Array.isArray( flags ) ) {
		for ( i = 0, len = flags.length; i < len; i++ ) {
			flag = flags[ i ];
			className = classPrefix + flag;
			// Set
			if ( !this.flags[ flag ] ) {
				changes[ flag ] = true;
				this.flags[ flag ] = true;
				add.push( className );
			}
		}
	} else if ( OO.isPlainObject( flags ) ) {
		for ( flag in flags ) {
			className = classPrefix + flag;
			if ( flags[ flag ] ) {
				// Set
				if ( !this.flags[ flag ] ) {
					changes[ flag ] = true;
					this.flags[ flag ] = true;
					add.push( className );
				}
			} else {
				// Remove
				if ( this.flags[ flag ] ) {
					changes[ flag ] = false;
					delete this.flags[ flag ];
					remove.push( className );
				}
			}
		}
	}

	if ( this.$flagged ) {
		this.$flagged
			.addClass( add.join( ' ' ) )
			.removeClass( remove.join( ' ' ) );
	}

	this.updateThemeClasses();
	this.emit( 'flag', changes );

	return this;
};
<commit_msg>FlaggedElement: Add description<commit_after>/**
 * The FlaggedElement class is an attribute mixin, meaning that it is used to add
 * additional functionality to an element created by another class. The class provides
 * a ‘flags’ property assigned the name (or an array of names) of styling flags,
 * which are used to customize the look and feel of a widget to better describe its
 * importance and functionality.
 *
 * The library currently contains the following styling flags for general use:
 *
 * - **progressive**:  Progressive styling is applied to convey that the widget will move the user forward in a process.
 * - **destructive**: Destructive styling is applied to convey that the widget will remove something.
 * - **constructive**: Constructive styling is applied to convey that the widget will create something.
 *
 * {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**.
 * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
 *
 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
 *
 * @abstract
 * @class
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {string|string[]} [flags] Flags describing importance and functionality, e.g. 'primary',
 *   'safe', 'progressive', 'destructive' or 'constructive'
 * @cfg {jQuery} [$flagged] Flagged node, assigned to #$flagged, omit to use #$element
 */
OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) {
	// Configuration initialization
	config = config || {};

	// Properties
	this.flags = {};
	this.$flagged = null;

	// Initialization
	this.setFlags( config.flags );
	this.setFlaggedElement( config.$flagged || this.$element );
};

/* Events */

/**
 * @event flag
 * @param {Object.<string,boolean>} changes Object keyed by flag name containing boolean
 *   added/removed properties
 */

/* Methods */

/**
 * Set the flagged element.
 *
 * If an element is already set, it will be cleaned up before setting up the new element.
 *
 * @param {jQuery} $flagged Element to add flags to
 */
OO.ui.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) {
	var classNames = Object.keys( this.flags ).map( function ( flag ) {
		return 'oo-ui-flaggedElement-' + flag;
	} ).join( ' ' );

	if ( this.$flagged ) {
		this.$flagged.removeClass( classNames );
	}

	this.$flagged = $flagged.addClass( classNames );
};

/**
 * Check if a flag is set.
 *
 * @param {string} flag Name of flag
 * @return {boolean} Has flag
 */
OO.ui.FlaggedElement.prototype.hasFlag = function ( flag ) {
	return flag in this.flags;
};

/**
 * Get the names of all flags set.
 *
 * @return {string[]} Flag names
 */
OO.ui.FlaggedElement.prototype.getFlags = function () {
	return Object.keys( this.flags );
};

/**
 * Clear all flags.
 *
 * @chainable
 * @fires flag
 */
OO.ui.FlaggedElement.prototype.clearFlags = function () {
	var flag, className,
		changes = {},
		remove = [],
		classPrefix = 'oo-ui-flaggedElement-';

	for ( flag in this.flags ) {
		className = classPrefix + flag;
		changes[ flag ] = false;
		delete this.flags[ flag ];
		remove.push( className );
	}

	if ( this.$flagged ) {
		this.$flagged.removeClass( remove.join( ' ' ) );
	}

	this.updateThemeClasses();
	this.emit( 'flag', changes );

	return this;
};

/**
 * Add one or more flags.
 *
 * @param {string|string[]|Object.<string, boolean>} flags One or more flags to add, or an object
 *  keyed by flag name containing boolean set/remove instructions.
 * @chainable
 * @fires flag
 */
OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) {
	var i, len, flag, className,
		changes = {},
		add = [],
		remove = [],
		classPrefix = 'oo-ui-flaggedElement-';

	if ( typeof flags === 'string' ) {
		className = classPrefix + flags;
		// Set
		if ( !this.flags[ flags ] ) {
			this.flags[ flags ] = true;
			add.push( className );
		}
	} else if ( Array.isArray( flags ) ) {
		for ( i = 0, len = flags.length; i < len; i++ ) {
			flag = flags[ i ];
			className = classPrefix + flag;
			// Set
			if ( !this.flags[ flag ] ) {
				changes[ flag ] = true;
				this.flags[ flag ] = true;
				add.push( className );
			}
		}
	} else if ( OO.isPlainObject( flags ) ) {
		for ( flag in flags ) {
			className = classPrefix + flag;
			if ( flags[ flag ] ) {
				// Set
				if ( !this.flags[ flag ] ) {
					changes[ flag ] = true;
					this.flags[ flag ] = true;
					add.push( className );
				}
			} else {
				// Remove
				if ( this.flags[ flag ] ) {
					changes[ flag ] = false;
					delete this.flags[ flag ];
					remove.push( className );
				}
			}
		}
	}

	if ( this.$flagged ) {
		this.$flagged
			.addClass( add.join( ' ' ) )
			.removeClass( remove.join( ' ' ) );
	}

	this.updateThemeClasses();
	this.emit( 'flag', changes );

	return this;
};
<|endoftext|>


<commit_before>describe('removeEventSource', function() {
  var options

  beforeEach(function() {
    affix('#cal')
    options = {
      defaultDate: '2014-08-01'
    }

    XHRMock.setup()
    XHRMock.get(/.*/, {
      status: 200,
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(buildEventArray())
    })
  })

  afterEach(function() {
    XHRMock.teardown()
  })

  describe('with a URL', function() {
    testInput('/myscript.php') // will go to mockjax
  })

  describe('with an array', function() {
    testInput(buildEventArray())
  })

  describe('with a function', function() {
    testInput(function(start, end, timezone, callback) {
      callback(buildEventArray())
    })
  })

  describe('with an object+url', function() {
    testInput({
      url: '/myscript.php' // will go to mockjax
    })
  })

  describe('with an object+array', function() {
    testInput({
      events: buildEventArray()
    })
  })

  describe('with an object+function', function() {
    testInput({
      events: function(start, end, timezone, callback) {
        callback(buildEventArray())
      }
    })
  })

  it('won\'t render removed events when subsequent addEventSource', function(done) {

    var source1 = function(start, end, timezone, callback) {
      setTimeout(function() {
        callback([ {
          title: 'event1',
          className: 'event1',
          start: '2014-08-01T02:00:00'
        } ])
      }, 100)
    }

    var source2 = function(start, end, timezone, callback) {
      setTimeout(function() {
        callback([ {
          title: 'event2',
          className: 'event2',
          start: '2014-08-01T02:00:00'
        } ])
      }, 100)
    }

    options.eventSources = [ source1 ]

    options.eventAfterAllRender = function() {
      if (!$('.fc-event').length) {
        ; // might have rendered no events after removeEventSource call
      } else {
        expect($('.event1').length).toBe(0)
        expect($('.event2').length).toBe(1)
        done()
      }
    }

    $('#cal').fullCalendar(options)
    $('#cal').fullCalendar('removeEventSource', source1)
    $('#cal').fullCalendar('addEventSource', source2)
  })

  describe('when multiple sources share the same fetching function', function() {
    var fetchFunc = function(start, end, timezone, callback) {
      callback([ {
        title: 'event',
        start: '2014-08-01T02:00:00'
      } ])
    }
    beforeEach(function() {
      options.eventSources = [
        { events: fetchFunc, className: 'event1', id: 'source1' },
        { events: fetchFunc, className: 'event2', id: 'source2' }
      ]
    })

    describe('when called with the raw function', function() {
      it('removes events from all matching sources', function() {

        $('#cal').fullCalendar(options)
        expect($('.fc-event').length).toBe(2)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(1)

        $('#cal').fullCalendar('removeEventSource', fetchFunc)
        expect($('.fc-event').length).toBe(0)
      })
    })

    describe('when called with proper source object', function() {
      it('removes events only from the specific source', function() {

        $('#cal').fullCalendar(options)
        expect($('.fc-event').length).toBe(2)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(1)

        var source = $('#cal').fullCalendar('getEventSourceById', 'source2')
        $('#cal').fullCalendar('removeEventSource', source)
        expect($('.fc-event').length).toBe(1)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(0)
      })
    })
  })

  function testInput(eventInput) {

    it('correctly removes events provided via `events` at initialization', function(done) {
      var callCnt = 0
      options.eventAfterAllRender = function() {
        callCnt++
        if (callCnt === 1) {
          expectEventCnt(2)
          $('#cal').fullCalendar('removeEventSource', eventInput)
        } else if (callCnt === 2) {
          expectEventCnt(0)
          done()
        }
      }
      options.events = eventInput
      $('#cal').fullCalendar(options)
    })

    it('correctly removes events provided via `eventSources` at initialization', function(done) {
      var callCnt = 0
      options.eventAfterAllRender = function() {
        callCnt++
        if (callCnt === 1) {
          expectEventCnt(2)
          $('#cal').fullCalendar('removeEventSource', eventInput)
        } else if (callCnt === 2) {
          expectEventCnt(0)
          done()
        }
      }
      options.eventSources = [ eventInput ]
      $('#cal').fullCalendar(options)
    })

    it('correctly removes events provided via `addEventSource` method', function(done) {
      var callCnt = 0
      options.eventAfterAllRender = function() {
        callCnt++
        if (callCnt === 1) {
          $('#cal').fullCalendar('addEventSource', eventInput)
        } else if (callCnt === 2) {
          expectEventCnt(2)
          $('#cal').fullCalendar('removeEventSource', eventInput)
        } else if (callCnt === 3) {
          expectEventCnt(0)
          done()
        }
      }
      $('#cal').fullCalendar(options)
    })
  }

  function buildEventArray() {
    return [
      { title: 'event1', start: '2014-08-01' },
      { title: 'event2', start: '2014-08-02' }
    ]
  }

  function expectEventCnt(cnt) {
    expect($('.fc-event').length).toBe(cnt)
    expect($('#cal').fullCalendar('clientEvents').length).toBe(cnt)
  }
})
<commit_msg>convert final file to new test scheme<commit_after>describe('removeEventSource', function() {
  pushOptions({
    defaultDate: '2014-08-01'
  })

  beforeEach(function() {
    XHRMock.setup()
    XHRMock.get(/.*/, {
      status: 200,
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(buildEventArray())
    })
  })

  afterEach(function() {
    XHRMock.teardown()
  })

  describe('with a URL', function() {
    testInput('/myscript.php') // will go to mockjax
  })

  describe('with an array', function() {
    testInput(buildEventArray())
  })

  describe('with a function', function() {
    testInput(function(start, end, timezone, callback) {
      callback(buildEventArray())
    })
  })

  describe('with an object+url', function() {
    testInput({
      url: '/myscript.php' // will go to mockjax
    })
  })

  describe('with an object+array', function() {
    testInput({
      events: buildEventArray()
    })
  })

  describe('with an object+function', function() {
    testInput({
      events: function(start, end, timezone, callback) {
        callback(buildEventArray())
      }
    })
  })

  it('won\'t render removed events when subsequent addEventSource', function(done) {

    var source1 = function(start, end, timezone, callback) {
      setTimeout(function() {
        callback([ {
          title: 'event1',
          className: 'event1',
          start: '2014-08-01T02:00:00'
        } ])
      }, 100)
    }

    var source2 = function(start, end, timezone, callback) {
      setTimeout(function() {
        callback([ {
          title: 'event2',
          className: 'event2',
          start: '2014-08-01T02:00:00'
        } ])
      }, 100)
    }

    initCalendar({
      eventSources: [ source1 ],
      eventAfterAllRender() {
        if (!$('.fc-event').length) {
          ; // might have rendered no events after removeEventSource call
        } else {
          expect($('.event1').length).toBe(0)
          expect($('.event2').length).toBe(1)
          done()
        }
      }
    })

    currentCalendar.removeEventSource(source1)
    currentCalendar.addEventSource(source2)
  })

  describe('when multiple sources share the same fetching function', function() {
    var fetchFunc = function(start, end, timezone, callback) {
      callback([ {
        title: 'event',
        start: '2014-08-01T02:00:00'
      } ])
    }

    pushOptions({
      eventSources: [
        { events: fetchFunc, className: 'event1', id: 'source1' },
        { events: fetchFunc, className: 'event2', id: 'source2' }
      ]
    })

    describe('when called with the raw function', function() {
      it('removes events from all matching sources', function() {

        initCalendar()
        expect($('.fc-event').length).toBe(2)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(1)

        currentCalendar.removeEventSource(fetchFunc)
        expect($('.fc-event').length).toBe(0)
      })
    })

    describe('when called with proper source object', function() {
      it('removes events only from the specific source', function() {

        initCalendar()
        expect($('.fc-event').length).toBe(2)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(1)

        var source = currentCalendar.getEventSourceById('source2')
        currentCalendar.removeEventSource(source)
        expect($('.fc-event').length).toBe(1)
        expect($('.event1').length).toBe(1)
        expect($('.event2').length).toBe(0)
      })
    })
  })

  function testInput(eventInput) {

    it('correctly removes events provided via `events` at initialization', function(done) {
      var callCnt = 0

      initCalendar({
        events: eventInput,
        eventAfterAllRender() {
          callCnt++
          if (callCnt === 1) {
            expectEventCnt(2)
            currentCalendar.removeEventSource(eventInput)
          } else if (callCnt === 2) {
            expectEventCnt(0)
            done()
          }
        }
      })
    })

    it('correctly removes events provided via `eventSources` at initialization', function(done) {
      var callCnt = 0

      initCalendar({
        eventSources: [ eventInput ],
        eventAfterAllRender() {
          callCnt++
          if (callCnt === 1) {
            expectEventCnt(2)
            currentCalendar.removeEventSource(eventInput)
          } else if (callCnt === 2) {
            expectEventCnt(0)
            done()
          }
        }
      })
    })

    it('correctly removes events provided via `addEventSource` method', function(done) {
      var callCnt = 0

      initCalendar({
        eventAfterAllRender() {
          callCnt++
          if (callCnt === 1) {
            currentCalendar.addEventSource(eventInput)
          } else if (callCnt === 2) {
            expectEventCnt(2)
            currentCalendar.removeEventSource(eventInput)
          } else if (callCnt === 3) {
            expectEventCnt(0)
            done()
          }
        }
      })
    })
  }

  function buildEventArray() {
    return [
      { title: 'event1', start: '2014-08-01' },
      { title: 'event2', start: '2014-08-02' }
    ]
  }

  function expectEventCnt(cnt) {
    expect($('.fc-event').length).toBe(cnt)
    expect(currentCalendar.clientEvents().length).toBe(cnt)
  }
})
<|endoftext|>


<commit_before>'use strict';

const stampit = require('stampit');
const $ = require('jquery');

const assert = require('chai').assert;
const sinon = require('sinon');

const buttonComponentStamp = require('../../src/components/button');
const domNavigationStamp = require('../../src/utilities/dom-navigation');
const eventEmitterEventsStamp = require('../../src/utilities/node-event-emitter-stamp');
const formEventsHandlerStamp = require('../../src/event-handlers/form-events-handler');

describe('Objects created using the `formEventsHandlerStamp`', function() {
  let formEventsHandler;
  let jQueryEvent;
  beforeEach(function() {
    jQueryEvent = new $.Event();
    formEventsHandler = formEventsHandlerStamp();
  });

  describe('`formSubmitHandler()`', function() {
    it('should emit a `formationFormSubmit` node event', function() {
      const nodeEvents = eventEmitterEventsStamp();
      const nodeEventsMock = sinon.mock(nodeEvents);

      nodeEventsMock.expects('emit').once().withArgs('formationFormSubmit', jQueryEvent);
      formEventsHandler = formEventsHandlerStamp({nodeEvents : nodeEvents});

      assert.isTrue(formEventsHandler.formSubmitHandler(jQueryEvent));

      nodeEventsMock.verify();
    });
  });

  describe('`checkFormValidityHandler()`', function() {
    beforeEach(function() { jQueryEvent.namespace = 'formation'; });
    describe('nothing should happen', function() {
      it('when the event namespace is undefined or not `formation`', function() {
        delete jQueryEvent.namespace; // so it's `undefined`
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));
        jQueryEvent.namespace = 'randomjibberish';
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));
      });
      it('when the continue (submit or preview) button cannot be found for the form', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);
        formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(null);

        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));

        formEventsHandlerMock.verify();
      });
      it('when continue button is already in a `submitting` state', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);
        let button = buttonComponentStamp();
        let buttonMock = sinon.mock(button);

        formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
        buttonMock.expects('isSubmitting').once().returns(true);
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));

        formEventsHandlerMock.verify();
        buttonMock.verify();
      });
    });
    describe('something should happen', function() {
      let formEventsHandlerMock;
      let button;
      let buttonMock;
      let $visibleRequired;
      let $visibleRequiredMock;
      beforeEach(function() {
        formEventsHandlerMock = sinon.mock(formEventsHandler);
        button = buttonComponentStamp();
        buttonMock = sinon.mock(button);
        $visibleRequired = $('<input />,<select></select>,<textarea></textarea>');
        $visibleRequiredMock = sinon.mock($visibleRequired);
      });
      describe('when all elements are not valid', function() {
        it('disables the `continue` button', function() {
          let $validRequiredFields = $('<input />,<select></select>');

          formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
          buttonMock.expects('isSubmitting').once().returns(false);
          formEventsHandlerMock.expects('get$requiredFields').once().returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs(domNavigationStamp().visibleEnabledFilter)
            .returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs('[data-valid="1"]')
            .returns($validRequiredFields);
          buttonMock.expects('setEnabled').once().withArgs(false);

          formEventsHandler.checkFormValidityHandler(jQueryEvent);

          formEventsHandlerMock.verify();
          buttonMock.verify();
          $visibleRequiredMock.verify();
        });
      });
      describe('when all elements are valid', function() {
        it('enables the `continue` button', function() {
          formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
          buttonMock.expects('isSubmitting').once().returns(false);
          formEventsHandlerMock.expects('get$requiredFields').once().returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs(domNavigationStamp().visibleEnabledFilter)
            .returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs('[data-valid="1"]')
            .returns($visibleRequired);
          buttonMock.expects('setEnabled').once().withArgs(true);

          formEventsHandler.checkFormValidityHandler(jQueryEvent);

          formEventsHandlerMock.verify();
          buttonMock.verify();
          $visibleRequiredMock.verify();
        });
      });
    });
  });

  describe('all the element event handlers', function() {
    let $fnMock;
    beforeEach(function() {
      $fnMock = sinon.mock($.fn);
    });
    afterEach(function() {
      $fnMock.restore();
    });
    describe('`checkBoxChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        jQueryEvent.target = $('<input type="checkbox" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));
        $fnMock.expects('is').once().withArgs(':checked').returns(false);
        formEventsHandlerMock.expects('showOrHideLinkedElement')
          .once().withArgs($(jQueryEvent.target), false);

        formEventsHandler.checkBoxChangeHandler(jQueryEvent);

        $fnMock.verify();
        formEventsHandlerMock.verify();
      });
    });

    describe('`radioChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<input type="radio" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.radioChangeHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`selectChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<select></select>').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.selectChangeHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`inputTextareaKeyUpHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<textarea></textarea>').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.inputTextareaKeyUpHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`inputFocusHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<input type="text" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.inputFocusHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`validateFormFields()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        jQueryEvent.target = $('<form></form>').get(0);
        let $requiredFields = $('<input />').add($('<select></select>')).add($('<textarea></textarea>'));
        let $optionalFields = $('<textarea></textarea>').add($('<input type="email" />'));
        formEventsHandlerMock.expects('get$requiredFields').once().returns($requiredFields);
        formEventsHandlerMock.expects('get$optionalFields').once().returns($optionalFields);
        let $fields = $().add($requiredFields).add($optionalFields);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($fields);

        formEventsHandler.validateFormFields(jQueryEvent);

        $fnMock.verify();
        formEventsHandlerMock.verify();
      });
    });

    describe('`formValidationHandler()`', function() {
      beforeEach(function() { jQueryEvent.namespace = 'formation'; });
      describe('nothing should happen', function() {
        it('when the event namespace is undefined or not `formation`', function () {
          delete jQueryEvent.namespace; // so it's `undefined`
          assert.isUndefined(formEventsHandler.formValidationHandler(jQueryEvent));
          jQueryEvent.namespace = 'randomjibberish';
          assert.isUndefined(formEventsHandler.formValidationHandler(jQueryEvent));
        });
      });
      describe('something should happen when the event namespace is `formation`', function() {
        it('should attempt to validate the target element and then trigger the forms `check-form-validity` event', function () {
          jQueryEvent.target = $('<input type="tel" />').get(0);
          let validator = { validate : function() {}};
          let $form = $('<form></form>');
          let $formMock = sinon.mock($form);
          let validatorMock = sinon.mock(validator);
          let formEventsHandlerMock = sinon.mock(formEventsHandler);

          formEventsHandlerMock.expects('getValidator').once().returns(validator);
          validatorMock.expects('validate').once().withArgs($(jQueryEvent.target));
          formEventsHandlerMock.expects('get$form').once().returns($form);
          $formMock.expects('trigger').once().withArgs('check-form-validity.formation');

          formEventsHandler.formValidationHandler(jQueryEvent);

          $formMock.verify();
          $fnMock.verify();
          formEventsHandlerMock.verify();
          validatorMock.verify();
        });
      });
    });
  });

  describe('`triggerValidationCheck()`', function() {
    it('should return `false` when the form events have not yet been initialized (as it is by default)', function() {
      let formEventsHandlerMock = sinon.mock(formEventsHandler);
      let $inputs = $();
      let $inputsMock = sinon.mock($inputs);

      formEventsHandlerMock.expects('getAllInputElementsToValidate').once().returns($inputs);
      $inputsMock.expects('trigger').once().withArgs('validation-handler.formation');
      assert.equal(formEventsHandler.triggerValidationCheck(), formEventsHandler);

      formEventsHandlerMock.verify();
      $inputsMock.verify();
    });
  });

  describe('`getAllInputElementsToValidate()`', function() {
    it('should return a set of jQuery-wrapped form input elements', function() {
      let $form = $('<form data-formation="1"></form>');
      let $formMock = sinon.mock($form);
      formEventsHandler = formEventsHandlerStamp();
      let formEventsHandlerMock = sinon.mock(formEventsHandler);

      formEventsHandlerMock.expects('get$form').once().returns($form);
      $formMock.expects('find').once().withArgs('input, textarea, select').returns($());

      formEventsHandler.getAllInputElementsToValidate();

      formEventsHandlerMock.verify();
      $formMock.verify();
    });
  });

  describe('`addDefaultEventHandlers()`', function() {
    describe('when the `$form` object is null', function() {
      it('should throw an Error', function() {
        formEventsHandler = formEventsHandlerStamp();
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('get$form').once().returns(null);
        assert.throws(() => formEventsHandler.addDefaultEventHandlers(), Error);

        formEventsHandlerMock.verify();
      });
    });

    describe('when the `$form` object is set', function() {
      it('should set a bunch of event handlers on various input elements (text, textarea, select, radio, checkbox)', function() {
        let $form = $('<form data-formation="1"></form>');
        let $formMock = sinon.mock($form);
        formEventsHandler = formEventsHandlerStamp();
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('get$form').once().returns($form);

        // See http://stackoverflow.com/questions/38387222/mocking-a-method-which-is-called-using-an-arrow-function-as-a-parameter
        $formMock.expects('submit').once().withArgs(sinon.match.func).returns($form);
        $formMock.expects('on').once()
          .withArgs('check-form-validity.formation', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'input:checkbox', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'input:radio', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('keyup.formation', 'input, textarea', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('validation-handler.formation', 'input, textarea, select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('focus.formation', 'input, textarea, select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('mouseenter.formation, mouseleave.formation, touchstart.formation', sinon.match.func)
          .returns($form);

        formEventsHandlerMock.expects('setEventsInitialized').once().withArgs(true).returns(formEventsHandler);

        // Call the SUT
        assert.equal(formEventsHandler.addDefaultEventHandlers(), formEventsHandler);

        $formMock.verify();
        formEventsHandlerMock.verify();
      });
    });
  });
});
<commit_msg>Implement tests for `initFormEvents()` method of `form-events-handler` stamp.<commit_after>'use strict';

const stampit = require('stampit');
const $ = require('jquery');

const assert = require('chai').assert;
const sinon = require('sinon');

const buttonComponentStamp = require('../../src/components/button');
const domNavigationStamp = require('../../src/utilities/dom-navigation');
const eventEmitterEventsStamp = require('../../src/utilities/node-event-emitter-stamp');
const formEventsHandlerStamp = require('../../src/event-handlers/form-events-handler');

describe('Objects created using the `formEventsHandlerStamp`', function() {
  let formEventsHandler;
  let jQueryEvent;
  beforeEach(function() {
    jQueryEvent = new $.Event();
    formEventsHandler = formEventsHandlerStamp();
  });

  describe('`formSubmitHandler()`', function() {
    it('should emit a `formationFormSubmit` node event', function() {
      const nodeEvents = eventEmitterEventsStamp();
      const nodeEventsMock = sinon.mock(nodeEvents);

      nodeEventsMock.expects('emit').once().withArgs('formationFormSubmit', jQueryEvent);
      formEventsHandler = formEventsHandlerStamp({nodeEvents : nodeEvents});

      assert.isTrue(formEventsHandler.formSubmitHandler(jQueryEvent));

      nodeEventsMock.verify();
    });
  });

  describe('`checkFormValidityHandler()`', function() {
    beforeEach(function() { jQueryEvent.namespace = 'formation'; });
    describe('nothing should happen', function() {
      it('when the event namespace is undefined or not `formation`', function() {
        delete jQueryEvent.namespace; // so it's `undefined`
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));
        jQueryEvent.namespace = 'randomjibberish';
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));
      });
      it('when the continue (submit or preview) button cannot be found for the form', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);
        formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(null);

        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));

        formEventsHandlerMock.verify();
      });
      it('when continue button is already in a `submitting` state', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);
        let button = buttonComponentStamp();
        let buttonMock = sinon.mock(button);

        formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
        buttonMock.expects('isSubmitting').once().returns(true);
        assert.isUndefined(formEventsHandler.checkFormValidityHandler(jQueryEvent));

        formEventsHandlerMock.verify();
        buttonMock.verify();
      });
    });
    describe('something should happen', function() {
      let formEventsHandlerMock;
      let button;
      let buttonMock;
      let $visibleRequired;
      let $visibleRequiredMock;
      beforeEach(function() {
        formEventsHandlerMock = sinon.mock(formEventsHandler);
        button = buttonComponentStamp();
        buttonMock = sinon.mock(button);
        $visibleRequired = $('<input />,<select></select>,<textarea></textarea>');
        $visibleRequiredMock = sinon.mock($visibleRequired);
      });
      describe('when all elements are not valid', function() {
        it('disables the `continue` button', function() {
          let $validRequiredFields = $('<input />,<select></select>');

          formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
          buttonMock.expects('isSubmitting').once().returns(false);
          formEventsHandlerMock.expects('get$requiredFields').once().returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs(domNavigationStamp().visibleEnabledFilter)
            .returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs('[data-valid="1"]')
            .returns($validRequiredFields);
          buttonMock.expects('setEnabled').once().withArgs(false);

          formEventsHandler.checkFormValidityHandler(jQueryEvent);

          formEventsHandlerMock.verify();
          buttonMock.verify();
          $visibleRequiredMock.verify();
        });
      });
      describe('when all elements are valid', function() {
        it('enables the `continue` button', function() {
          formEventsHandlerMock.expects('getSubmitWithFallbackPreviewButton').once().returns(button);
          buttonMock.expects('isSubmitting').once().returns(false);
          formEventsHandlerMock.expects('get$requiredFields').once().returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs(domNavigationStamp().visibleEnabledFilter)
            .returns($visibleRequired);
          $visibleRequiredMock.expects('filter')
            .once().withArgs('[data-valid="1"]')
            .returns($visibleRequired);
          buttonMock.expects('setEnabled').once().withArgs(true);

          formEventsHandler.checkFormValidityHandler(jQueryEvent);

          formEventsHandlerMock.verify();
          buttonMock.verify();
          $visibleRequiredMock.verify();
        });
      });
    });
  });

  describe('all the element event handlers', function() {
    let $fnMock;
    beforeEach(function() {
      $fnMock = sinon.mock($.fn);
    });
    afterEach(function() {
      $fnMock.restore();
    });
    describe('`checkBoxChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        jQueryEvent.target = $('<input type="checkbox" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));
        $fnMock.expects('is').once().withArgs(':checked').returns(false);
        formEventsHandlerMock.expects('showOrHideLinkedElement')
          .once().withArgs($(jQueryEvent.target), false);

        formEventsHandler.checkBoxChangeHandler(jQueryEvent);

        $fnMock.verify();
        formEventsHandlerMock.verify();
      });
    });

    describe('`radioChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<input type="radio" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.radioChangeHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`selectChangeHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<select></select>').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.selectChangeHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`inputTextareaKeyUpHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<textarea></textarea>').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.inputTextareaKeyUpHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`inputFocusHandler()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        jQueryEvent.target = $('<input type="text" />').get(0);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($(jQueryEvent.target));

        formEventsHandler.inputFocusHandler(jQueryEvent);
        $fnMock.verify();
      });
    });

    describe('`validateFormFields()`', function() {
      it('should trigger the `validation-handler.formation` event on the event target element', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        jQueryEvent.target = $('<form></form>').get(0);
        let $requiredFields = $('<input />').add($('<select></select>')).add($('<textarea></textarea>'));
        let $optionalFields = $('<textarea></textarea>').add($('<input type="email" />'));
        formEventsHandlerMock.expects('get$requiredFields').once().returns($requiredFields);
        formEventsHandlerMock.expects('get$optionalFields').once().returns($optionalFields);
        let $fields = $().add($requiredFields).add($optionalFields);
        $fnMock.expects('trigger').once().withArgs('validation-handler.formation').returns($fields);

        formEventsHandler.validateFormFields(jQueryEvent);

        $fnMock.verify();
        formEventsHandlerMock.verify();
      });
    });

    describe('`formValidationHandler()`', function() {
      beforeEach(function() { jQueryEvent.namespace = 'formation'; });
      describe('nothing should happen', function() {
        it('when the event namespace is undefined or not `formation`', function () {
          delete jQueryEvent.namespace; // so it's `undefined`
          assert.isUndefined(formEventsHandler.formValidationHandler(jQueryEvent));
          jQueryEvent.namespace = 'randomjibberish';
          assert.isUndefined(formEventsHandler.formValidationHandler(jQueryEvent));
        });
      });
      describe('something should happen when the event namespace is `formation`', function() {
        it('should attempt to validate the target element and then trigger the forms `check-form-validity` event', function () {
          jQueryEvent.target = $('<input type="tel" />').get(0);
          let validator = { validate : function() {}};
          let $form = $('<form></form>');
          let $formMock = sinon.mock($form);
          let validatorMock = sinon.mock(validator);
          let formEventsHandlerMock = sinon.mock(formEventsHandler);

          formEventsHandlerMock.expects('getValidator').once().returns(validator);
          validatorMock.expects('validate').once().withArgs($(jQueryEvent.target));
          formEventsHandlerMock.expects('get$form').once().returns($form);
          $formMock.expects('trigger').once().withArgs('check-form-validity.formation');

          formEventsHandler.formValidationHandler(jQueryEvent);

          $formMock.verify();
          $fnMock.verify();
          formEventsHandlerMock.verify();
          validatorMock.verify();
        });
      });
    });
  });

  describe('`initFormEvents()`', function() {
    describe('it is already initialized', function() {
      it('sees that it already initialized and returns', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('log').once().withArgs('Initializing form events...');
        formEventsHandlerMock.expects('getEventsInitialized').once().returns(true);
        formEventsHandlerMock.expects('info').once().withArgs('Form events previously initialized for this form, skipping.');

        assert.equal(formEventsHandler.initFormEvents(), formEventsHandler);

        formEventsHandlerMock.verify();
      });
    });
    describe('it is not yet initialized', function() {
      it('sees that it has not yet initialized, and initializes', function() {
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('log').once().withArgs('Initializing form events...');
        formEventsHandlerMock.expects('getEventsInitialized').once().returns(false);
        formEventsHandlerMock.expects('info').never();
        formEventsHandlerMock.expects('getLogConsole').once().returns(true);
        formEventsHandlerMock.expects('initLogging').once().withArgs(true).returns(formEventsHandler);
        formEventsHandlerMock.expects('addDefaultEventHandlers').once().returns(formEventsHandler);
        formEventsHandlerMock.expects('triggerValidationCheck').once().returns(formEventsHandler);

        assert.equal(formEventsHandler.initFormEvents(), formEventsHandler);

        formEventsHandlerMock.verify();
      });
    });
  });

  describe('`triggerValidationCheck()`', function() {
    it('should return `false` when the form events have not yet been initialized (as it is by default)', function() {
      let formEventsHandlerMock = sinon.mock(formEventsHandler);
      let $inputs = $();
      let $inputsMock = sinon.mock($inputs);

      formEventsHandlerMock.expects('getAllInputElementsToValidate').once().returns($inputs);
      $inputsMock.expects('trigger').once().withArgs('validation-handler.formation');
      assert.equal(formEventsHandler.triggerValidationCheck(), formEventsHandler);

      formEventsHandlerMock.verify();
      $inputsMock.verify();
    });
  });

  describe('`getAllInputElementsToValidate()`', function() {
    it('should return a set of jQuery-wrapped form input elements', function() {
      let $form = $('<form data-formation="1"></form>');
      let $formMock = sinon.mock($form);
      formEventsHandler = formEventsHandlerStamp();
      let formEventsHandlerMock = sinon.mock(formEventsHandler);

      formEventsHandlerMock.expects('get$form').once().returns($form);
      $formMock.expects('find').once().withArgs('input, textarea, select').returns($());

      formEventsHandler.getAllInputElementsToValidate();

      formEventsHandlerMock.verify();
      $formMock.verify();
    });
  });

  describe('`addDefaultEventHandlers()`', function() {
    describe('when the `$form` object is null', function() {
      it('should throw an Error', function() {
        formEventsHandler = formEventsHandlerStamp();
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('get$form').once().returns(null);
        assert.throws(() => formEventsHandler.addDefaultEventHandlers(), Error);

        formEventsHandlerMock.verify();
      });
    });

    describe('when the `$form` object is set', function() {
      it('should set a bunch of event handlers on various input elements (text, textarea, select, radio, checkbox)', function() {
        let $form = $('<form data-formation="1"></form>');
        let $formMock = sinon.mock($form);
        formEventsHandler = formEventsHandlerStamp();
        let formEventsHandlerMock = sinon.mock(formEventsHandler);

        formEventsHandlerMock.expects('get$form').once().returns($form);

        // See http://stackoverflow.com/questions/38387222/mocking-a-method-which-is-called-using-an-arrow-function-as-a-parameter
        $formMock.expects('submit').once().withArgs(sinon.match.func).returns($form);
        $formMock.expects('on').once()
          .withArgs('check-form-validity.formation', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'input:checkbox', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'input:radio', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('change.formation', 'select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('keyup.formation', 'input, textarea', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('validation-handler.formation', 'input, textarea, select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('focus.formation', 'input, textarea, select', sinon.match.func)
          .returns($form);
        $formMock.expects('on').once()
          .withArgs('mouseenter.formation, mouseleave.formation, touchstart.formation', sinon.match.func)
          .returns($form);

        formEventsHandlerMock.expects('setEventsInitialized').once().withArgs(true).returns(formEventsHandler);

        // Call the SUT
        assert.equal(formEventsHandler.addDefaultEventHandlers(), formEventsHandler);

        $formMock.verify();
        formEventsHandlerMock.verify();
      });
    });
  });
});
<|endoftext|>


<commit_before>Game.HeartController = function (gameState, player, waypoints) {
	this.gameState = gameState;
	this.game = this.gameState.game;
	this.player = player;
	this.waypoints = waypoints;
	this.sprite;

	this.currentAnimation;
	this.tweens = new Array();
	this.pursuitPositions = new Array();
	this.backwards = false;
	this.currentWaypointIndex;

	this.beatSprite;
	this.beatSpriteTween;

	this._enabled = true;
};

Game.HeartController.prototype = {
	create: function () {
		this.sprite = this.game.add.sprite(0, 0, 'main_sprite_atlas');
		this.sprite.frameName = 'heart_idle_1_100-100.png';
		this.sprite.anchor.setTo(0.5, 0.5);

		// Create animations
		var idleAnimFrames = new Array();
        for (var i = 1; i <= 4; i++) {
            idleAnimFrames.push('heart_idle_' + i + '_100-100.png');
        };

        var flyAnimFrames = new Array();
        for (var i = 1; i <= 10; i++) {
            flyAnimFrames.push('heart_flying_' + i + '_100-100.png');
        };

        this.sprite.animations.add('idle', idleAnimFrames, 10, true);
        this.sprite.animations.add('fly', flyAnimFrames, 10, true);

        this.playIdleAnimation();

        this.currentWaypointIndex = Utils.random(0, this.waypoints.polyline.length - 1);
        // console.log('currentWaypointIndex: ' + this.currentWaypointIndex);

        this.sprite.x = this.waypoints.x + this.waypoints.polyline[this.currentWaypointIndex][0];
        this.sprite.y = this.waypoints.y + this.waypoints.polyline[this.currentWaypointIndex][1];

        this.flyToNextWaypoint(HeartConsts.delayToNextWaypoint);
	},

	setEnabled: function (value) {
		this._enabled = value;

		if (value) {
			this.resumeTweens();
		} else {
			this.pauseTweens();
		}
	},

	playBeat: function () {
		if (!this._enabled ||
		    this.pursuitPositions.length != 0 ||
			Phaser.Point.distance(this.player.sprite, this.sprite) > HeartConsts.minimumBeatDistance) {
			return;
		}

		var mySprite = this.sprite;
        var beatSprite = this.beatSprite;

		if (!beatSprite) {
			beatSprite = this.game.add.sprite(mySprite.x, mySprite.y, 'main_sprite_atlas');
			beatSprite.anchor.setTo(0.5, 0.5);
			this.beatSprite = beatSprite;
		} else {
			beatSprite.revive();
		}

		beatSprite.frameName = mySprite.frameName;
		beatSprite.x = mySprite.x;
		beatSprite.y = mySprite.y;
		beatSprite.alpha = 0.5;

		var beatAnimation = this.game.add.tween(beatSprite);
		this.beatSpriteTween = beatAnimation;
        beatAnimation.to({ alpha: 1.0 }, 250, Phaser.Easing.Quadratic.Out, true, 0);

		var self = this;

		beatAnimation.onComplete.add(function () {
			beatSprite.alpha = 0.5;
			beatAnimation = self.game.add.tween(beatSprite);
			self.beatSpriteTween = beatAnimation;
			beatAnimation.to({ alpha: 1.0 }, 250, Phaser.Easing.Quadratic.Out, true, 0);

	        beatAnimation.onComplete.add(function () {
	            beatSprite.alpha = 1.0;
	            beatAnimation = self.game.add.tween(beatSprite);
				self.beatSpriteTween = beatAnimation;
	            beatAnimation.to({ alpha: 0.0 }, 500, Phaser.Easing.Quadratic.Out, true, 0);

				beatAnimation.onComplete.add(function () {
					beatSprite.kill();
					self.beatSpriteTween = null;
				});
	        });
		});
	},

	update: function () {
		if (!this._enabled) {
			return;
		}

		var mySprite = this.sprite;
		var beatSprite = this.beatSprite;
		var player = this.player;
		var playerSprite = player.sprite;
		var playerDistance = Phaser.Point.distance(playerSprite, mySprite);
		var pursuitPositions = this.pursuitPositions;

		if (beatSprite) {
			beatSprite.x = mySprite.x;
            beatSprite.y = mySprite.y;
			beatSprite.frameName = mySprite.frameName;
		}

        if (player.currentAnim != 'dying' &&
            player.currentAnim != Game.PlayerController.AnimState.FlyingSoul) {

			if (playerDistance <= HeartConsts.attackDistance) {
	            this.pauseTweens();

	            if (pursuitPositions.length == 0) {
	                pursuitPositions.push({
	                    x: mySprite.x,
	                    y: mySprite.y
	                });
	            }

	            Utils.advanceSpriteToPosition(mySprite, playerSprite, HeartConsts.pursuitVelocity);
	        } else {
	            if (pursuitPositions.length > 0) {
	                var position = pursuitPositions[pursuitPositions.length - 1];
	                Utils.advanceSpriteToPosition(mySprite, position, HeartConsts.pursuitVelocity);

	                if (mySprite.x == position.x && mySprite.y == position.y) {
	                    pursuitPositions.splice(pursuitPositions.length - 1, 1);
	                }

	//              pursuitPositions.splice(pursuitPositions.length - 1, 1);
	//              mySprite.x = position.x;
	//              mySprite.y = position.y;
	            } else {
	                this.resumeTweens();
	            }
	        }

			if (playerSprite.overlap(mySprite) && playerDistance <= HeartConsts.playerDeathDistance) {
	            // console.log('call onPlayerLostLife');
//				pursuitPositions.splice(pursuitPositions.length - 1, 1);
	            player.playDeathAnimation();
	            player.destroyBody();
	            this.gameState.onPlayerLostLife();
	        }
		}
	},

	resumeTweens: function () {
		var tweens = this.tweens;
		var length = tweens.length;
		for (var i = 0; i < length; i++) {
			if (!tweens[i].isRunning) {
				tweens[i].resume();
			}
		}

		if (this.beatSpriteTween && !this.beatSpriteTween.isRunning) {
			this.beatSpriteTween.resume();
		}
	},

	pauseTweens: function () {
		var tweens = this.tweens;
        var length = tweens.length;
        for (var i = 0; i < length; i++) {
            if (tweens[i].isRunning) {
				tweens[i].pause();
				tweens[i].isRunning = false;
			}
        }

		if (this.beatSpriteTween && this.beatSpriteTween.isRunning) {
			this.beatSpriteTween.pause();
			this.beatSpriteTween.isRunning = false;
		}
	},

	getNextWaypoint: function (previousWaypoint) {
		if (typeof(previousWaypoint) !== 'number')
			return;

		var nextWaypoint;

		if (!this.backwards) {
			nextWaypoint = previousWaypoint + 1;

			if (nextWaypoint >= this.waypoints.polyline.length) {
				this.backwards = true;
				nextWaypoint = previousWaypoint - 1;
			}

		} else {
			nextWaypoint = previousWaypoint - 1;

			if (nextWaypoint < 0) {
				this.backwards = false;
				nextWaypoint = previousWaypoint + 1;
			}
		}

		// console.log('getNextWaypoint => prev: ' + previousWaypoint + ', next: ' + nextWaypoint);

		return nextWaypoint;
	},

	flyToNextWaypoint: function (delay) {
		var previousWaypoint = this.waypoints.polyline[this.currentWaypointIndex];
		var targetWaypointIndex = this.getNextWaypoint(this.currentWaypointIndex);
		var targetWaypoint = this.waypoints.polyline[targetWaypointIndex];

		var distance = Phaser.Point.distance(new Phaser.Point(targetWaypoint[0], targetWaypoint[1]),
											 new Phaser.Point(previousWaypoint[0], previousWaypoint[1]));
		var animTime = (HeartConsts.flightSpeed * distance) / 10;

		Utils.clearArray(this.tweens);

		var flyTween = this.game.add.tween(this.sprite);
		this.tweens.push(flyTween);
        flyTween.to({ x: this.waypoints.x + targetWaypoint[0] }, animTime, Phaser.Easing.Linear.None, true, delay);

        flyTween = this.game.add.tween(this.sprite);
		this.tweens.push(flyTween);
		flyTween.to({ y: this.waypoints.y + targetWaypoint[1] }, animTime, Phaser.Easing.Linear.None, true, delay);
		flyTween.onStart.add(this.playFlyAnimation, this);
	    flyTween.onComplete.add(this.onWaypointReached, this);

        this.currentWaypointIndex = targetWaypointIndex;
	},

	onWaypointReached: function () {
		// console.log('onWaypointReached');
		this.playIdleAnimation();
		this.flyToNextWaypoint(HeartConsts.delayToNextWaypoint);
	},

	playIdleAnimation: function () {
		this.sprite.animations.play('idle');
		this.currentAnimation = 'idle';
	},

	playFlyAnimation: function () {
		this.sprite.animations.play('fly');
		this.currentAnimation = 'fly';
	}
};
<commit_msg>Killing heartbeat sprite after the player was damaged.<commit_after>Game.HeartController = function (gameState, player, waypoints) {
	this.gameState = gameState;
	this.game = this.gameState.game;
	this.player = player;
	this.waypoints = waypoints;
	this.sprite;

	this.currentAnimation;
	this.tweens = new Array();
	this.pursuitPositions = new Array();
	this.backwards = false;
	this.currentWaypointIndex;

	this.beatSprite;
	this.beatSpriteTween;

	this._enabled = true;
};

Game.HeartController.prototype = {
	create: function () {
		this.sprite = this.game.add.sprite(0, 0, 'main_sprite_atlas');
		this.sprite.frameName = 'heart_idle_1_100-100.png';
		this.sprite.anchor.setTo(0.5, 0.5);

		// Create animations
		var idleAnimFrames = new Array();
        for (var i = 1; i <= 4; i++) {
            idleAnimFrames.push('heart_idle_' + i + '_100-100.png');
        };

        var flyAnimFrames = new Array();
        for (var i = 1; i <= 10; i++) {
            flyAnimFrames.push('heart_flying_' + i + '_100-100.png');
        };

        this.sprite.animations.add('idle', idleAnimFrames, 10, true);
        this.sprite.animations.add('fly', flyAnimFrames, 10, true);

        this.playIdleAnimation();

        this.currentWaypointIndex = Utils.random(0, this.waypoints.polyline.length - 1);
        // console.log('currentWaypointIndex: ' + this.currentWaypointIndex);

        this.sprite.x = this.waypoints.x + this.waypoints.polyline[this.currentWaypointIndex][0];
        this.sprite.y = this.waypoints.y + this.waypoints.polyline[this.currentWaypointIndex][1];

        this.flyToNextWaypoint(HeartConsts.delayToNextWaypoint);
	},

	setEnabled: function (value) {
		this._enabled = value;

		if (value) {
			this.resumeTweens();
		} else {
			this.pauseTweens();
		}
	},

	playBeat: function () {
		if (!this._enabled ||
		    this.pursuitPositions.length != 0 ||
			Phaser.Point.distance(this.player.sprite, this.sprite) > HeartConsts.minimumBeatDistance) {
			return;
		}

		var mySprite = this.sprite;
        var beatSprite = this.beatSprite;

		if (!beatSprite) {
			beatSprite = this.game.add.sprite(mySprite.x, mySprite.y, 'main_sprite_atlas');
			beatSprite.anchor.setTo(0.5, 0.5);
			this.beatSprite = beatSprite;
		} else {
			beatSprite.revive();
		}

		beatSprite.frameName = mySprite.frameName;
		beatSprite.x = mySprite.x;
		beatSprite.y = mySprite.y;
		beatSprite.alpha = 0.5;

		var beatAnimation = this.game.add.tween(beatSprite);
		this.beatSpriteTween = beatAnimation;
        beatAnimation.to({ alpha: 1.0 }, 250, Phaser.Easing.Quadratic.Out, true, 0);

		var self = this;

		beatAnimation.onComplete.add(function () {
			beatSprite.alpha = 0.5;
			beatAnimation = self.game.add.tween(beatSprite);
			self.beatSpriteTween = beatAnimation;
			beatAnimation.to({ alpha: 1.0 }, 250, Phaser.Easing.Quadratic.Out, true, 0);

	        beatAnimation.onComplete.add(function () {
	            beatSprite.alpha = 1.0;
	            beatAnimation = self.game.add.tween(beatSprite);
				self.beatSpriteTween = beatAnimation;
	            beatAnimation.to({ alpha: 0.0 }, 500, Phaser.Easing.Quadratic.Out, true, 0);

				beatAnimation.onComplete.add(function () {
					beatSprite.kill();
					self.beatSpriteTween = null;
				});
	        });
		});
	},

	update: function () {
		if (!this._enabled) {
			return;
		}

		var mySprite = this.sprite;
		var beatSprite = this.beatSprite;
		var player = this.player;
		var playerSprite = player.sprite;
		var playerDistance = Phaser.Point.distance(playerSprite, mySprite);
		var pursuitPositions = this.pursuitPositions;

		if (beatSprite) {
			beatSprite.x = mySprite.x;
            beatSprite.y = mySprite.y;
			beatSprite.frameName = mySprite.frameName;
		}

        if (player.currentAnim != 'dying' &&
            player.currentAnim != Game.PlayerController.AnimState.FlyingSoul) {

			if (playerDistance <= HeartConsts.attackDistance) {
	            this.pauseTweens();

	            if (pursuitPositions.length == 0) {
	                pursuitPositions.push({
	                    x: mySprite.x,
	                    y: mySprite.y
	                });
	            }

	            Utils.advanceSpriteToPosition(mySprite, playerSprite, HeartConsts.pursuitVelocity);
	        } else {
	            if (pursuitPositions.length > 0) {
	                var position = pursuitPositions[pursuitPositions.length - 1];
	                Utils.advanceSpriteToPosition(mySprite, position, HeartConsts.pursuitVelocity);

	                if (mySprite.x == position.x && mySprite.y == position.y) {
	                    pursuitPositions.splice(pursuitPositions.length - 1, 1);
	                }

	//              pursuitPositions.splice(pursuitPositions.length - 1, 1);
	//              mySprite.x = position.x;
	//              mySprite.y = position.y;
	            } else {
	                this.resumeTweens();
	            }
	        }

			if (playerSprite.overlap(mySprite) && playerDistance <= HeartConsts.playerDeathDistance) {
//				pursuitPositions.splice(pursuitPositions.length - 1, 1);
				beatSprite.kill();
	            player.playDeathAnimation();
	            player.destroyBody();
	            this.gameState.onPlayerLostLife();
	        }
		}
	},

	resumeTweens: function () {
		var tweens = this.tweens;
		var length = tweens.length;
		for (var i = 0; i < length; i++) {
			if (!tweens[i].isRunning) {
				tweens[i].resume();
			}
		}

		if (this.beatSpriteTween && !this.beatSpriteTween.isRunning) {
			this.beatSpriteTween.resume();
		}
	},

	pauseTweens: function () {
		var tweens = this.tweens;
        var length = tweens.length;
        for (var i = 0; i < length; i++) {
            if (tweens[i].isRunning) {
				tweens[i].pause();
				tweens[i].isRunning = false;
			}
        }

		if (this.beatSpriteTween && this.beatSpriteTween.isRunning) {
			this.beatSpriteTween.pause();
			this.beatSpriteTween.isRunning = false;
		}
	},

	getNextWaypoint: function (previousWaypoint) {
		if (typeof(previousWaypoint) !== 'number')
			return;

		var nextWaypoint;

		if (!this.backwards) {
			nextWaypoint = previousWaypoint + 1;

			if (nextWaypoint >= this.waypoints.polyline.length) {
				this.backwards = true;
				nextWaypoint = previousWaypoint - 1;
			}

		} else {
			nextWaypoint = previousWaypoint - 1;

			if (nextWaypoint < 0) {
				this.backwards = false;
				nextWaypoint = previousWaypoint + 1;
			}
		}

		// console.log('getNextWaypoint => prev: ' + previousWaypoint + ', next: ' + nextWaypoint);

		return nextWaypoint;
	},

	flyToNextWaypoint: function (delay) {
		var previousWaypoint = this.waypoints.polyline[this.currentWaypointIndex];
		var targetWaypointIndex = this.getNextWaypoint(this.currentWaypointIndex);
		var targetWaypoint = this.waypoints.polyline[targetWaypointIndex];

		var distance = Phaser.Point.distance(new Phaser.Point(targetWaypoint[0], targetWaypoint[1]),
											 new Phaser.Point(previousWaypoint[0], previousWaypoint[1]));
		var animTime = (HeartConsts.flightSpeed * distance) / 10;

		Utils.clearArray(this.tweens);

		var flyTween = this.game.add.tween(this.sprite);
		this.tweens.push(flyTween);
        flyTween.to({ x: this.waypoints.x + targetWaypoint[0] }, animTime, Phaser.Easing.Linear.None, true, delay);

        flyTween = this.game.add.tween(this.sprite);
		this.tweens.push(flyTween);
		flyTween.to({ y: this.waypoints.y + targetWaypoint[1] }, animTime, Phaser.Easing.Linear.None, true, delay);
		flyTween.onStart.add(this.playFlyAnimation, this);
	    flyTween.onComplete.add(this.onWaypointReached, this);

        this.currentWaypointIndex = targetWaypointIndex;
	},

	onWaypointReached: function () {
		// console.log('onWaypointReached');
		this.playIdleAnimation();
		this.flyToNextWaypoint(HeartConsts.delayToNextWaypoint);
	},

	playIdleAnimation: function () {
		this.sprite.animations.play('idle');
		this.currentAnimation = 'idle';
	},

	playFlyAnimation: function () {
		this.sprite.animations.play('fly');
		this.currentAnimation = 'fly';
	}
};
<|endoftext|>


<commit_before>

import './assign_to';
import './attachments';
import './share';
import './review';
import './document_follow';
import './user_image';
import './form_sidebar_users';

frappe.ui.form.Sidebar = class {
	constructor(opts) {
		$.extend(this, opts);
	}

	make () {
		var sidebar_content = frappe.render_template("form_sidebar", {doctype: this.frm.doctype, frm:this.frm});

		this.sidebar = $('

')
			.html(sidebar_content)
			.appendTo(this.page.sidebar.empty());

		this.comments = this.sidebar.find(".form-sidebar-stats .comments");
		this.user_actions = this.sidebar.find(".user-actions");
		this.image_section = this.sidebar.find(".sidebar-image-section");
		this.image_wrapper = this.image_section.find('.sidebar-image-wrapper');
		this.make_assignments();
		this.make_attachments();
		this.make_review();
		this.make_shared();

		this.make_tags();
		this.make_like();
		this.make_follow();

		this.bind_events();
		this.setup_keyboard_shortcuts();
		this.show_auto_repeat_status();
		frappe.ui.form.setup_user_image_event(this.frm);

		this.refresh();

	}

	bind_events () {
		var me = this;

		// scroll to comments
		this.comments.on("click", function() {
			frappe.utils.scroll_to(me.frm.footer.wrapper.find(".comment-box"), true);
		});

		this.like_icon.on("click", function() {
			frappe.ui.toggle_like(me.like_wrapper, me.frm.doctype, me.frm.doc.name, function() {
				me.refresh_like();
			});
		});
	}

	setup_keyboard_shortcuts() {
		// add assignment shortcut
		let assignment_link = this.sidebar.find('.add-assignment');
		frappe.ui.keys
			.get_shortcut_group(this.page)
			.add(assignment_link);
	}

	refresh () {
		if (this.frm.doc.__islocal) {
			this.sidebar.toggle(false);
		} else {
			this.sidebar.toggle(true);
			this.frm.assign_to.refresh();
			this.frm.attachments.refresh();
			this.frm.shared.refresh();

			this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags);

			if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) {
				let route = this.frm.doc.route;
				frappe.utils.get_page_view_count(route).then((res) => {
					this.sidebar
						.find(".pageview-count")
						.html(
							__("{0} Page Views", [String(res.message).bold()])
						);
				});
			}

			this.sidebar
				.find(".modified-by")
				.html(
					__("{0} edited this {1}", [
						frappe.user.full_name(this.frm.doc.modified_by).bold(),
						"
" + comment_when(this.frm.doc.modified),
					], "For example, 'Jon Doe edited this 5 minutes ago'.")
				);
			this.sidebar
				.find(".created-by")
				.html(
					__("{0} created this {1}", [
						frappe.user.full_name(this.frm.doc.owner).bold(),
						"
" + comment_when(this.frm.doc.creation),
					], "For example, 'Jon Doe created this 5 minutes ago'.")
				);

			this.refresh_like();
			this.refresh_follow();
			this.refresh_comments_count();
			frappe.ui.form.set_user_image(this.frm);
		}
	}

	show_auto_repeat_status() {
		if (this.frm.meta.allow_auto_repeat && this.frm.doc.auto_repeat) {
			const me = this;
			frappe.call({
				method: "frappe.client.get_value",
				args:{
					doctype: "Auto Repeat",
					filters: {
						name: this.frm.doc.auto_repeat
					},
					fieldname: ["frequency"]
				},
				callback: function(res) {
					me.sidebar.find(".auto-repeat-status").html(__("Repeats {0}", [res.message.frequency]));
					me.sidebar.find(".auto-repeat-status").on("click", function(){
						frappe.set_route("Form", "Auto Repeat", me.frm.doc.auto_repeat);
					});
				}
			});
		}
	}

	make_tags() {
		if (this.frm.meta.issingle) {
			this.sidebar.find(".form-tags").toggle(false);
			return;
		}

		let tags_parent = this.sidebar.find(".form-tags");

		this.frm.tags = new frappe.ui.TagEditor({
			parent: tags_parent,
			add_button: tags_parent.find(".add-tags-btn"),
			frm: this.frm,
			on_change: function(user_tags) {
				this.frm.tags && this.frm.tags.refresh(user_tags);
			}
		});
	}

	make_attachments() {
		var me = this;
		this.frm.attachments = new frappe.ui.form.Attachments({
			parent: me.sidebar.find(".form-attachments"),
			frm: me.frm
		});
	}

	make_assignments() {
		this.frm.assign_to = new frappe.ui.form.AssignTo({
			parent: this.sidebar.find(".form-assignments"),
			frm: this.frm
		});
	}

	make_shared() {
		this.frm.shared = new frappe.ui.form.Share({
			frm: this.frm,
			parent: this.sidebar.find(".form-shared")
		});
	}

	add_user_action(label, click) {
		return $('').html(label).appendTo($('
')
			.appendTo(this.user_actions.removeClass("hidden"))).on("click", click);
	}

	clear_user_actions() {
		this.user_actions.addClass("hidden")
		this.user_actions.find(".user-action-row").remove();
	}

	make_like() {
		this.like_wrapper = this.sidebar.find(".liked-by");
		this.like_icon = this.sidebar.find(".liked-by .like-icon");
		this.like_count = this.sidebar.find(".liked-by .like-count");
		frappe.ui.setup_like_popover(this.sidebar.find(".form-stats-likes"), ".like-icon");
	}

	make_follow() {
		this.follow_button = this.sidebar.find(".form-sidebar-stats .form-follow");

		this.follow_button.on('click', () => {
			let is_followed = this.frm.get_docinfo().is_document_followed;
			frappe.call('frappe.desk.form.document_follow.update_follow', {
				'doctype': this.frm.doctype,
				'doc_name': this.frm.doc.name,
				'following': !is_followed
			}).then(() => {
				frappe.model.set_docinfo(this.frm.doctype, this.frm.doc.name, "is_document_followed", !is_followed);
				this.refresh_follow(!is_followed);
			});
		});
	}

	refresh_follow(follow) {
		if (follow == null) {
			follow = this.frm.get_docinfo().is_document_followed;
		}
		this.follow_button.text(follow ? "Unfollow" : "Follow");
	}

	refresh_like() {
		if (!this.like_icon) {
			return;
		}

		this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by);
		const liked = frappe.ui.is_liked(this.frm.doc);
		this.like_wrapper
			.toggleClass("not-liked", !liked)
			.toggleClass("liked", liked)
			.attr("data-doctype", this.frm.doctype)
			.attr("data-name", this.frm.doc.name);

		this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length);
	}

	refresh_comments_count() {
		let count = (this.frm.get_docinfo().comments || []).length;
		this.comments.find(".comments-count").html(count);
	}

	refresh_image() {
	}

	make_review() {
		const review_wrapper = this.sidebar.find(".form-reviews");
		if (frappe.boot.energy_points_enabled && !this.frm.is_new()) {
			this.frm.reviews = new frappe.ui.form.Review({
				parent: review_wrapper,
				frm: this.frm
			});
		} else {
			review_wrapper.remove();
		}
	}

	reload_docinfo(callback) {
		frappe.call({
			method: "frappe.desk.form.load.get_docinfo",
			args: {
				doctype: this.frm.doctype,
				name: this.frm.docname
			},
			callback: (r) => {
				// docinfo will be synced
				if(callback) callback(r.docinfo);
				this.frm.timeline && this.frm.timeline.refresh();
				this.frm.assign_to.refresh();
				this.frm.attachments.refresh();
			}
		});
	}

};
<commit_msg>Update form_sidebar.js<commit_after>

import './assign_to';
import './attachments';
import './share';
import './review';
import './document_follow';
import './user_image';
import './form_sidebar_users';

frappe.ui.form.Sidebar = class {
	constructor(opts) {
		$.extend(this, opts);
	}

	make () {
		var sidebar_content = frappe.render_template("form_sidebar", {doctype: this.frm.doctype, frm:this.frm});

		this.sidebar = $('

')
			.html(sidebar_content)
			.appendTo(this.page.sidebar.empty());

		this.comments = this.sidebar.find(".form-sidebar-stats .comments");
		this.user_actions = this.sidebar.find(".user-actions");
		this.image_section = this.sidebar.find(".sidebar-image-section");
		this.image_wrapper = this.image_section.find('.sidebar-image-wrapper');
		this.make_assignments();
		this.make_attachments();
		this.make_review();
		this.make_shared();

		this.make_tags();
		this.make_like();
		this.make_follow();

		this.bind_events();
		this.setup_keyboard_shortcuts();
		this.show_auto_repeat_status();
		frappe.ui.form.setup_user_image_event(this.frm);

		this.refresh();

	}

	bind_events () {
		var me = this;

		// scroll to comments
		this.comments.on("click", function() {
			frappe.utils.scroll_to(me.frm.footer.wrapper.find(".comment-box"), true);
		});

		this.like_icon.on("click", function() {
			frappe.ui.toggle_like(me.like_wrapper, me.frm.doctype, me.frm.doc.name, function() {
				me.refresh_like();
			});
		});
	}

	setup_keyboard_shortcuts() {
		// add assignment shortcut
		let assignment_link = this.sidebar.find('.add-assignment');
		frappe.ui.keys
			.get_shortcut_group(this.page)
			.add(assignment_link);
	}

	refresh () {
		if (this.frm.doc.__islocal) {
			this.sidebar.toggle(false);
		} else {
			this.sidebar.toggle(true);
			this.frm.assign_to.refresh();
			this.frm.attachments.refresh();
			this.frm.shared.refresh();

			this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags);

			if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) {
				let route = this.frm.doc.route;
				frappe.utils.get_page_view_count(route).then((res) => {
					this.sidebar
						.find(".pageview-count")
						.html(
							__("{0} Page Views", [String(res.message).bold()])
						);
				});
			}

			this.sidebar
				.find(".modified-by")
				.html(
					__("{0} edited this {1}", [
						frappe.user.full_name(this.frm.doc.modified_by).bold(),
						"
" + comment_when(this.frm.doc.modified),
					], "For example, 'Jon Doe edited this 5 minutes ago'.")
				);
			this.sidebar
				.find(".created-by")
				.html(
					__("{0} created this {1}", [
						frappe.user.full_name(this.frm.doc.owner).bold(),
						"
" + comment_when(this.frm.doc.creation),
					], "For example, 'Jon Doe created this 5 minutes ago'.")
				);

			this.refresh_like();
			this.refresh_follow();
			this.refresh_comments_count();
			frappe.ui.form.set_user_image(this.frm);
		}
	}

	show_auto_repeat_status() {
		if (this.frm.meta.allow_auto_repeat && this.frm.doc.auto_repeat) {
			const me = this;
			frappe.call({
				method: "frappe.client.get_value",
				args:{
					doctype: "Auto Repeat",
					filters: {
						name: this.frm.doc.auto_repeat
					},
					fieldname: ["frequency"]
				},
				callback: function(res) {
					me.sidebar.find(".auto-repeat-status").html(__("Repeats {0}", [res.message.frequency]));
					me.sidebar.find(".auto-repeat-status").on("click", function(){
						frappe.set_route("Form", "Auto Repeat", me.frm.doc.auto_repeat);
					});
				}
			});
		}
	}

	make_tags() {
		if (this.frm.meta.issingle) {
			this.sidebar.find(".form-tags").toggle(false);
			return;
		}

		let tags_parent = this.sidebar.find(".form-tags");

		this.frm.tags = new frappe.ui.TagEditor({
			parent: tags_parent,
			add_button: tags_parent.find(".add-tags-btn"),
			frm: this.frm,
			on_change: function(user_tags) {
				this.frm.tags && this.frm.tags.refresh(user_tags);
			}
		});
	}

	make_attachments() {
		var me = this;
		this.frm.attachments = new frappe.ui.form.Attachments({
			parent: me.sidebar.find(".form-attachments"),
			frm: me.frm
		});
	}

	make_assignments() {
		this.frm.assign_to = new frappe.ui.form.AssignTo({
			parent: this.sidebar.find(".form-assignments"),
			frm: this.frm
		});
	}

	make_shared() {
		this.frm.shared = new frappe.ui.form.Share({
			frm: this.frm,
			parent: this.sidebar.find(".form-shared")
		});
	}

	add_user_action(label, click) {
		return $('').html(label).appendTo($('
')
			.appendTo(this.user_actions.removeClass("hidden"))).on("click", click);
	}

	clear_user_actions() {
		this.user_actions.addClass("hidden")
		this.user_actions.find(".user-action-row").remove();
	}

	make_like() {
		this.like_wrapper = this.sidebar.find(".liked-by");
		this.like_icon = this.sidebar.find(".liked-by .like-icon");
		this.like_count = this.sidebar.find(".liked-by .like-count");
		frappe.ui.setup_like_popover(this.sidebar.find(".form-stats-likes"), ".like-icon");
	}

	make_follow() {
		this.follow_button = this.sidebar.find(".form-sidebar-stats .form-follow");

		this.follow_button.on('click', () => {
			let is_followed = this.frm.get_docinfo().is_document_followed;
			frappe.call('frappe.desk.form.document_follow.update_follow', {
				'doctype': this.frm.doctype,
				'doc_name': this.frm.doc.name,
				'following': !is_followed
			}).then(() => {
				frappe.model.set_docinfo(this.frm.doctype, this.frm.doc.name, "is_document_followed", !is_followed);
				this.refresh_follow(!is_followed);
			});
		});
	}

	refresh_follow(follow) {
		if (follow == null) {
			follow = this.frm.get_docinfo().is_document_followed;
		}
		this.follow_button.text(follow ? __("Unfollow") : __("Follow"));
	}

	refresh_like() {
		if (!this.like_icon) {
			return;
		}

		this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by);
		const liked = frappe.ui.is_liked(this.frm.doc);
		this.like_wrapper
			.toggleClass("not-liked", !liked)
			.toggleClass("liked", liked)
			.attr("data-doctype", this.frm.doctype)
			.attr("data-name", this.frm.doc.name);

		this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length);
	}

	refresh_comments_count() {
		let count = (this.frm.get_docinfo().comments || []).length;
		this.comments.find(".comments-count").html(count);
	}

	refresh_image() {
	}

	make_review() {
		const review_wrapper = this.sidebar.find(".form-reviews");
		if (frappe.boot.energy_points_enabled && !this.frm.is_new()) {
			this.frm.reviews = new frappe.ui.form.Review({
				parent: review_wrapper,
				frm: this.frm
			});
		} else {
			review_wrapper.remove();
		}
	}

	reload_docinfo(callback) {
		frappe.call({
			method: "frappe.desk.form.load.get_docinfo",
			args: {
				doctype: this.frm.doctype,
				name: this.frm.docname
			},
			callback: (r) => {
				// docinfo will be synced
				if(callback) callback(r.docinfo);
				this.frm.timeline && this.frm.timeline.refresh();
				this.frm.assign_to.refresh();
				this.frm.attachments.refresh();
			}
		});
	}

};
<|endoftext|>


<commit_before>import { autorun, observable, reaction } from 'mobx';
import moment from 'moment';
import DelayAppComponent from './Component';

import { DEFAULT_FEATURES_CONFIG } from '../../config';
import { gaEvent, gaPage } from '../../lib/analytics';

const debug = require('debug')('Franz:feature:delayApp');

export const config = {
  delayOffset: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset,
  delayDuration: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait,
};

export const state = observable({
  isDelayAppScreenVisible: DEFAULT_FEATURES_CONFIG.needToWaitToProceed,
});

function setVisibility(value) {
  Object.assign(state, {
    isDelayAppScreenVisible: value,
  });
}

export default function init(stores) {
  debug('Initializing `delayApp` feature');

  let shownAfterLaunch = false;
  let timeLastDelay = moment();

  window.franz.features.delayApp = {
    state,
  };

  reaction(
    () => stores.user.isLoggedIn && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium,
    (isEnabled) => {
      if (isEnabled) {
        debug('Enabling `delayApp` feature');

        const { needToWaitToProceedConfig: globalConfig } = stores.features.features;

        config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset;
        config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait;

        autorun(() => {
          if (stores.services.all.length === 0) {
            shownAfterLaunch = true;
            return;
          }

          const diff = moment().diff(timeLastDelay);
          if ((stores.app.isFocused && diff >= config.delayOffset) || !shownAfterLaunch) {
            debug(`App will be delayed for ${config.delayDuration / 1000}s`);

            setVisibility(true);
            gaPage('/delayApp');
            gaEvent('DelayApp', 'show', 'Delay App Feature');

            timeLastDelay = moment();
            shownAfterLaunch = true;

            setTimeout(() => {
              debug('Resetting app delay');

              setVisibility(false);
            }, DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait + 1000); // timer needs to be able to hit 0
          }
        });
      } else {
        setVisibility(false);
      }
    },
  );
}

export const Component = DelayAppComponent;
<commit_msg>Fix delay app logic<commit_after>import { autorun, observable, reaction } from 'mobx';
import moment from 'moment';
import DelayAppComponent from './Component';

import { DEFAULT_FEATURES_CONFIG } from '../../config';
import { gaEvent, gaPage } from '../../lib/analytics';

const debug = require('debug')('Franz:feature:delayApp');

export const config = {
  delayOffset: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset,
  delayDuration: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait,
};

export const state = observable({
  isDelayAppScreenVisible: DEFAULT_FEATURES_CONFIG.needToWaitToProceed,
});

function setVisibility(value) {
  Object.assign(state, {
    isDelayAppScreenVisible: value,
  });
}

export default function init(stores) {
  debug('Initializing `delayApp` feature');

  let shownAfterLaunch = false;
  let timeLastDelay = moment();

  window.franz.features.delayApp = {
    state,
  };

  reaction(
    () => stores.user.isLoggedIn && stores.services.allServicesRequest.wasExecuted && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium,
    (isEnabled) => {
      if (isEnabled) {
        debug('Enabling `delayApp` feature');

        const { needToWaitToProceedConfig: globalConfig } = stores.features.features;

        config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset;
        config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait;

        autorun(() => {
          if (stores.services.all.length === 0) {
            debug('seas', stores.services.all.length);
            shownAfterLaunch = true;
            return;
          }

          const diff = moment().diff(timeLastDelay);
          if ((stores.app.isFocused && diff >= config.delayOffset) || !shownAfterLaunch) {
            debug(`App will be delayed for ${config.delayDuration / 1000}s`);

            setVisibility(true);
            gaPage('/delayApp');
            gaEvent('DelayApp', 'show', 'Delay App Feature');

            timeLastDelay = moment();
            shownAfterLaunch = true;

            setTimeout(() => {
              debug('Resetting app delay');

              setVisibility(false);
            }, config.delayDuration + 1000); // timer needs to be able to hit 0
          }
        });
      } else {
        setVisibility(false);
      }
    },
  );
}

export const Component = DelayAppComponent;
<|endoftext|>