<commit_before>const AudioPlayer = require('../audio-player');
const CordovaMediaPlayer = require('../cordova-media-player');
const Stopwatch = require('../tools').Stopwatch;
const WebAudioPlayer = require('../web-audio-player');

describe('Audio player', () => {
  let oldMedia;
  let oldAudio;

  beforeEach(() => {
    oldMedia = window.Media;
    oldAudio = window.Audio;
    spyOn(AudioPlayer.prototype, '_playbackCompatibility');
    spyOn(AudioPlayer.prototype, '_getBestPlayer');
  });

  afterEach(() => {
    window.Media = oldMedia;
    window.Audio = oldAudio;
  });

  it('should construct with event functionality', () => {
    const player = new AudioPlayer();
    player.emitter = jasmine.createSpyObj('emitter', ['on', 'off', 'emit']);
    player.resetEventListeners();
    player.addEventListener('evt1', () => {});
    player.removeEventListener('evt1', () => {});
    player.emitter.emit('evt1', ['args']);
    player.emitter.emit('evt2');
    expect(player._playbackCompatibility).toHaveBeenCalledTimes(1);
    expect(player._getBestPlayer).toHaveBeenCalledTimes(1);
    const callbacks = player._getBestPlayer.calls.mostRecent().args[0];
    expect(player.resetEventListeners).toEqual(jasmine.any(Function));
    expect(player.addEventListener).toEqual(jasmine.any(Function));
    expect(player.removeEventListener).toEqual(jasmine.any(Function));
    expect(player.emitter.emit).toEqual(jasmine.any(Function));
    for (const callback in callbacks) {
      callbacks[callback]();
    }
    expect(player.emitter.on).toHaveBeenCalledWith('evt1', jasmine.any(Function));
    expect(player.emitter.off).toHaveBeenCalledWith('evt1', jasmine.any(Function));
    expect(player.emitter.emit).toHaveBeenCalledWith('evt1', ['args']);
    expect(player.emitter.emit).toHaveBeenCalledWith('evt2');
  });

  describe('Compatibility', () => {
    beforeEach(() => {
      window.Media = jasmine.createSpy();
      window.Audio = jasmine.createSpy();
    });

    it('should recognize playback compatibility when all are available', () => {
      window.Audio.and.returnValue({
        canPlayType: () => 1
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeTruthy();
      expect(player.canUseCordovaMedia).toBeTruthy();
    });

    it('should recognize playback compatibility when only cordova is available', () => {
      window.Audio = undefined;
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeFalsy();
      expect(player.canUseCordovaMedia).toBeTruthy();
    });

    it('should recognize playback compatibility when only HTML5 is available', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: () => 1
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeTruthy();
      expect(player.canUseCordovaMedia).toBeFalsy();
    });

    it('should recognize playback compatibility when only HTML5 is available, but has no methods.', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: 'Error'
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Unable to detect audio playback capabilities');
    });

    it('should recognize playback compatibility when none are available', () => {
      window.Media = undefined;
      window.Audio = undefined;
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Some form of audio playback capability is required');
    });

    it('should recognize playback compatibility when neither wave nor mp3 can be played', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: () => ''
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Native Wave or MP3 playback is required');
    });
  });

  it('should get the best player when it can use cordova', () => {
    window.device = {platform: 'Android'};
    const player = new AudioPlayer();
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    player.canUseCordovaMedia = true;
    const result = player._getBestPlayer();
    expect(result).toEqual(jasmine.any(CordovaMediaPlayer));
    delete window.device;
  });

  it('should get the best player when it can use HTML5', () => {
    const oldAudio_ = window.Audio;
    window.Audio = jasmine.createSpy().and.returnValue({
      canPlayType: () => 1
    });
    spyOn(WebAudioPlayer.prototype, '_initPlayer');
    const player = new AudioPlayer();
    player.canUseAudio = true;
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    const result = player._getBestPlayer();
    expect(result).toEqual(jasmine.any(WebAudioPlayer));
    window.Audio = oldAudio_;
  });

  it('should get the best player when it can use neither', () => {
    const player = new AudioPlayer();
    player.canUseCordovaMedia = false;
    player.canUseAudio = false;
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    expect(() => {
      player._getBestPlayer();
    }).toThrowError('Unable to find a proper player.');
  });

  it('should load from an url with preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['load']);
    const loadCb = jasmine.createSpy();
    spyOn(player.emitter, 'emit');
    player.load('url', true, loadCb);
    expect(player.player.load).toHaveBeenCalled();
    expect(player.emitter.emit).not.toHaveBeenCalled();
  });

  it('should load from an url without preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['load']);
    const loadCb = jasmine.createSpy();
    spyOn(player.emitter, 'emit');
    player.load('url', false, loadCb);
    expect(player.player.load).toHaveBeenCalledWith('url', false, loadCb);
    expect(player.emitter.emit).toHaveBeenCalledWith('canplay', []);
  });

  it('should reset', () => {
    const player = new AudioPlayer();
    spyOn(player, 'stop');
    spyOn(player.emitter, 'emit');
    player.player = jasmine.createSpyObj('player', ['reset']);
    player.reset();
    expect(player.player.reset).toHaveBeenCalledTimes(1);
    expect(player.stop).toHaveBeenCalledTimes(1);
    expect(player.emitter.emit).toHaveBeenCalledTimes(1);
  });

  it('should play', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'isPlaying']);
    player.player.isPlaying.and.returnValue(false);
    player.play(40);
    expect(player.player.play).toHaveBeenCalledWith(40);
  });

  it('should not play when already playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    player.play(40);
    expect(player.player.play).not.toHaveBeenCalled();
  });

  it('should stop', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['stop']);
    player.stop();
    expect(player.player.stop).toHaveBeenCalledTimes(1);
  });

  it('should pause', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['pause']);
    player.pause();
    expect(player.player.pause).toHaveBeenCalledTimes(1);
  });

  it('should toggle playback when playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'pause', 'isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    player.togglePlayback();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(1);
    expect(player.player.pause).toHaveBeenCalledTimes(1);
    expect(player.player.play).not.toHaveBeenCalled();
  });

  it('should toggle playback when not playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'pause', 'isPlaying']);
    player.player.isPlaying.and.returnValue(false);
    player.togglePlayback();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(2);
    expect(player.player.pause).not.toHaveBeenCalledTimes(1);
    expect(player.player.play).toHaveBeenCalledTimes(1);
  });

  it('should preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['preload']);
    player.preload();
    expect(player.player.preload).toHaveBeenCalledTimes(1);
  });

  it('should scrub', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['scrub']);
    player.scrub(50);
    expect(player.player.scrub).toHaveBeenCalledWith(50);
  });

  it('should get buffer fill', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getBufferFill']);
    player.player.getBufferFill.and.returnValue(10);
    const result = player.getBufferFill();
    expect(player.player.getBufferFill).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should get current time', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getCurrentTime']);
    player.player.getCurrentTime.and.returnValue(10);
    const result = player.getCurrentTime();
    expect(player.player.getCurrentTime).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should get duration', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getDuration']);
    player.player.getDuration.and.returnValue(10);
    const result = player.getDuration();
    expect(player.player.getDuration).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should return playing state', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    const result = player.isPlaying();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(1);
    expect(result).toBeTruthy();
  });

  it('should return ready state', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['canPlay']);
    player.player.canPlay.and.returnValue(true);
    const result = player.canPlay();
    expect(player.player.canPlay).toHaveBeenCalledTimes(1);
    expect(result).toBeTruthy();
  });

  it('should bind and return stopwatch', () => {
    const player = new AudioPlayer();
    const cb = jasmine.createSpy();
    player.player = jasmine.createSpyObj('player', ['bindStopwatch']);
    player.player.bindStopwatch.and.returnValue(new Stopwatch());
    const result = player.bindStopwatch(cb);
    expect(result).toEqual(jasmine.any(Stopwatch));
  });

  it('should bind the play function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['play', 'getCurrentTime', 'isPlaying']);
    fakePlayer.getCurrentTime.and.returnValue(1);
    fakePlayer.isPlaying.and.returnValue(false);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['start']);
    fakeWatch.value = 10;
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.play();
    expect(fakeWatch.value).toEqual(10);
    expect(fakeWatch.start).toHaveBeenCalledTimes(1);
  });

  it('should bind the pause function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['pause']);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['stop']);
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.pause();
    expect(fakeWatch.stop).toHaveBeenCalledTimes(1);
  });

  it('should bind the stop function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['stop']);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['reset', 'stop']);
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.stop();
    expect(fakeWatch.reset).toHaveBeenCalledTimes(1);
    expect(fakeWatch.stop).toHaveBeenCalledTimes(1);
  });

  it('should bind a stopwatch and scrub audio', () => {
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['reset']);
    fakeWatch.value = 0;
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['scrub', 'getCurrentTime']);
    player.player.getCurrentTime.and.returnValue(1);
    player.stopwatch = fakeWatch;
    player.scrub(50);
    expect(player.player.getCurrentTime).toHaveBeenCalledTimes(1);
    expect(fakeWatch.value).toEqual(10);
  });

  it('should bind the reset function with a stopwatch', () => {
    const player = new AudioPlayer();
    player.stop = jasmine.createSpy();
    player.player = jasmine.createSpyObj('player', ['reset']);
    player.reset();
    expect(player.stop).toHaveBeenCalledTimes(1);
  });
});

<commit_msg>test(audioplayer): add event test<commit_after>const AudioPlayer = require('../audio-player');
const CordovaMediaPlayer = require('../cordova-media-player');
const Stopwatch = require('../tools').Stopwatch;
const WebAudioPlayer = require('../web-audio-player');

describe('Audio player', () => {
  let oldMedia;
  let oldAudio;

  beforeEach(() => {
    oldMedia = window.Media;
    oldAudio = window.Audio;
    spyOn(AudioPlayer.prototype, '_playbackCompatibility');
    spyOn(AudioPlayer.prototype, '_getBestPlayer');
  });

  afterEach(() => {
    window.Media = oldMedia;
    window.Audio = oldAudio;
  });

  it('should construct with event functionality', () => {
    const player = new AudioPlayer();
    let playbackStoppedCb = null;
    player.emitter = jasmine.createSpyObj('emitter', ['on', 'off', 'emit']);
    player.resetEventListeners();
    player.addEventListener('evt1', () => {});
    player.removeEventListener('evt1', () => {});
    player.emitter.emit('evt1', ['args']);
    player.emitter.emit('evt2');
    expect(player._playbackCompatibility).toHaveBeenCalledTimes(1);
    expect(player._getBestPlayer).toHaveBeenCalledTimes(1);
    const callbacks = player._getBestPlayer.calls.mostRecent().args[0];
    expect(player.resetEventListeners).toEqual(jasmine.any(Function));
    expect(player.addEventListener).toEqual(jasmine.any(Function));
    expect(player.removeEventListener).toEqual(jasmine.any(Function));
    expect(player.emitter.emit).toEqual(jasmine.any(Function));
    for (const callback in callbacks) {
      if (callback === 'playbackStoppedCb') {
        playbackStoppedCb = callbacks[callback];
      }
      callbacks[callback]();
    }
    expect(player.emitter.on).toHaveBeenCalledWith('evt1', jasmine.any(Function));
    expect(player.emitter.off).toHaveBeenCalledWith('evt1', jasmine.any(Function));
    expect(player.emitter.emit).toHaveBeenCalledWith('evt1', ['args']);
    expect(player.emitter.emit).toHaveBeenCalledWith('evt2');
    player.stopwatch = jasmine.createSpyObj('stopwatch', ['stop']);
    playbackStoppedCb();
    expect(player.stopwatch.stop).toHaveBeenCalledTimes(1);
  });

  describe('Compatibility', () => {
    beforeEach(() => {
      window.Media = jasmine.createSpy();
      window.Audio = jasmine.createSpy();
    });

    it('should recognize playback compatibility when all are available', () => {
      window.Audio.and.returnValue({
        canPlayType: () => 1
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeTruthy();
      expect(player.canUseCordovaMedia).toBeTruthy();
    });

    it('should recognize playback compatibility when only cordova is available', () => {
      window.Audio = undefined;
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeFalsy();
      expect(player.canUseCordovaMedia).toBeTruthy();
    });

    it('should recognize playback compatibility when only HTML5 is available', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: () => 1
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      player._playbackCompatibility();
      expect(player.canUseAudio).toBeTruthy();
      expect(player.canUseCordovaMedia).toBeFalsy();
    });

    it('should recognize playback compatibility when only HTML5 is available, but has no methods.', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: 'Error'
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Unable to detect audio playback capabilities');
    });

    it('should recognize playback compatibility when none are available', () => {
      window.Media = undefined;
      window.Audio = undefined;
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Some form of audio playback capability is required');
    });

    it('should recognize playback compatibility when neither wave nor mp3 can be played', () => {
      window.Media = undefined;
      window.Audio.and.returnValue({
        canPlayType: () => ''
      });
      const player = new AudioPlayer();
      player._playbackCompatibility.and.callThrough();
      expect(() => {
        player._playbackCompatibility();
      }).toThrowError('Native Wave or MP3 playback is required');
    });
  });

  it('should get the best player when it can use cordova', () => {
    window.device = {platform: 'Android'};
    const player = new AudioPlayer();
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    player.canUseCordovaMedia = true;
    const result = player._getBestPlayer();
    expect(result).toEqual(jasmine.any(CordovaMediaPlayer));
    delete window.device;
  });

  it('should get the best player when it can use HTML5', () => {
    const oldAudio_ = window.Audio;
    window.Audio = jasmine.createSpy().and.returnValue({
      canPlayType: () => 1
    });
    spyOn(WebAudioPlayer.prototype, '_initPlayer');
    const player = new AudioPlayer();
    player.canUseAudio = true;
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    const result = player._getBestPlayer();
    expect(result).toEqual(jasmine.any(WebAudioPlayer));
    window.Audio = oldAudio_;
  });

  it('should get the best player when it can use neither', () => {
    const player = new AudioPlayer();
    player.canUseCordovaMedia = false;
    player.canUseAudio = false;
    AudioPlayer.prototype._getBestPlayer.and.callThrough();
    expect(() => {
      player._getBestPlayer();
    }).toThrowError('Unable to find a proper player.');
  });

  it('should load from an url with preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['load']);
    const loadCb = jasmine.createSpy();
    spyOn(player.emitter, 'emit');
    player.load('url', true, loadCb);
    expect(player.player.load).toHaveBeenCalled();
    expect(player.emitter.emit).not.toHaveBeenCalled();
  });

  it('should load from an url without preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['load']);
    const loadCb = jasmine.createSpy();
    spyOn(player.emitter, 'emit');
    player.load('url', false, loadCb);
    expect(player.player.load).toHaveBeenCalledWith('url', false, loadCb);
    expect(player.emitter.emit).toHaveBeenCalledWith('canplay', []);
  });

  it('should reset', () => {
    const player = new AudioPlayer();
    spyOn(player, 'stop');
    spyOn(player.emitter, 'emit');
    player.player = jasmine.createSpyObj('player', ['reset']);
    player.reset();
    expect(player.player.reset).toHaveBeenCalledTimes(1);
    expect(player.stop).toHaveBeenCalledTimes(1);
    expect(player.emitter.emit).toHaveBeenCalledTimes(1);
  });

  it('should play', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'isPlaying']);
    player.player.isPlaying.and.returnValue(false);
    player.play(40);
    expect(player.player.play).toHaveBeenCalledWith(40);
  });

  it('should not play when already playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    player.play(40);
    expect(player.player.play).not.toHaveBeenCalled();
  });

  it('should stop', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['stop']);
    player.stop();
    expect(player.player.stop).toHaveBeenCalledTimes(1);
  });

  it('should pause', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['pause']);
    player.pause();
    expect(player.player.pause).toHaveBeenCalledTimes(1);
  });

  it('should toggle playback when playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'pause', 'isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    player.togglePlayback();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(1);
    expect(player.player.pause).toHaveBeenCalledTimes(1);
    expect(player.player.play).not.toHaveBeenCalled();
  });

  it('should toggle playback when not playing', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['play', 'pause', 'isPlaying']);
    player.player.isPlaying.and.returnValue(false);
    player.togglePlayback();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(2);
    expect(player.player.pause).not.toHaveBeenCalledTimes(1);
    expect(player.player.play).toHaveBeenCalledTimes(1);
  });

  it('should preload', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['preload']);
    player.preload();
    expect(player.player.preload).toHaveBeenCalledTimes(1);
  });

  it('should scrub', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['scrub']);
    player.scrub(50);
    expect(player.player.scrub).toHaveBeenCalledWith(50);
  });

  it('should get buffer fill', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getBufferFill']);
    player.player.getBufferFill.and.returnValue(10);
    const result = player.getBufferFill();
    expect(player.player.getBufferFill).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should get current time', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getCurrentTime']);
    player.player.getCurrentTime.and.returnValue(10);
    const result = player.getCurrentTime();
    expect(player.player.getCurrentTime).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should get duration', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['getDuration']);
    player.player.getDuration.and.returnValue(10);
    const result = player.getDuration();
    expect(player.player.getDuration).toHaveBeenCalledTimes(1);
    expect(result).toEqual(10);
  });

  it('should return playing state', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['isPlaying']);
    player.player.isPlaying.and.returnValue(true);
    const result = player.isPlaying();
    expect(player.player.isPlaying).toHaveBeenCalledTimes(1);
    expect(result).toBeTruthy();
  });

  it('should return ready state', () => {
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['canPlay']);
    player.player.canPlay.and.returnValue(true);
    const result = player.canPlay();
    expect(player.player.canPlay).toHaveBeenCalledTimes(1);
    expect(result).toBeTruthy();
  });

  it('should bind and return stopwatch', () => {
    const player = new AudioPlayer();
    const cb = jasmine.createSpy();
    player.player = jasmine.createSpyObj('player', ['bindStopwatch']);
    player.player.bindStopwatch.and.returnValue(new Stopwatch());
    const result = player.bindStopwatch(cb);
    expect(result).toEqual(jasmine.any(Stopwatch));
  });

  it('should bind the play function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['play', 'getCurrentTime', 'isPlaying']);
    fakePlayer.getCurrentTime.and.returnValue(1);
    fakePlayer.isPlaying.and.returnValue(false);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['start']);
    fakeWatch.value = 10;
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.play();
    expect(fakeWatch.value).toEqual(10);
    expect(fakeWatch.start).toHaveBeenCalledTimes(1);
  });

  it('should bind the pause function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['pause']);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['stop']);
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.pause();
    expect(fakeWatch.stop).toHaveBeenCalledTimes(1);
  });

  it('should bind the stop function to the stopwatch', () => {
    const fakePlayer = jasmine.createSpyObj('stopwatch', ['stop']);
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['reset', 'stop']);
    const player = new AudioPlayer();
    player.player = fakePlayer;
    player.stopwatch = fakeWatch;
    player.stop();
    expect(fakeWatch.reset).toHaveBeenCalledTimes(1);
    expect(fakeWatch.stop).toHaveBeenCalledTimes(1);
  });

  it('should bind a stopwatch and scrub audio', () => {
    const fakeWatch = jasmine.createSpyObj('stopwatch', ['reset']);
    fakeWatch.value = 0;
    const player = new AudioPlayer();
    player.player = jasmine.createSpyObj('player', ['scrub', 'getCurrentTime']);
    player.player.getCurrentTime.and.returnValue(1);
    player.stopwatch = fakeWatch;
    player.scrub(50);
    expect(player.player.getCurrentTime).toHaveBeenCalledTimes(1);
    expect(fakeWatch.value).toEqual(10);
  });

  it('should bind the reset function with a stopwatch', () => {
    const player = new AudioPlayer();
    player.stop = jasmine.createSpy();
    player.player = jasmine.createSpyObj('player', ['reset']);
    player.reset();
    expect(player.stop).toHaveBeenCalledTimes(1);
  });
});

<|endoftext|>


<commit_before>/*global $, _, ko, N*/

'use strict';

////////////////////////////////////////////////////////////////////////////////

var
render          = require('../../../lib/render/client'),
embedded_fonts  = require('../../../lib/shared/embedded_fonts'),
glyphs_map      = require('../../../lib/shared/glyphs_map');

// Int to char, with fix for big numbers
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/fromCharCode
function fixedFromCharCode(code) {
  /*jshint bitwise: false*/
  if (code > 0xffff) {
    code -= 0x10000;
    var surrogate1 = 0xd800 + (code >> 10),
        surrogate2 = 0xdc00 + (code & 0x3ff);
    return String.fromCharCode(surrogate1, surrogate2);
  } else {
    return String.fromCharCode(code);
  }
}

// Char to Int, with fix for big numbers
function fixedCharCodeAt(char) {
  /*jshint bitwise: false*/
  var char1 = char.charCodeAt(0),
      char2 = char.charCodeAt(1);

  if ((char.length >= 2) &&
      ((char1 & 0xfc00) === 0xd800) &&
      ((char2 & 0xfc00) === 0xdc00)) {
    return 0x10000 + ((char1 - 0xd800) << 10) + (char2 - 0xdc00);
  } else {
    return char1;
  }
}

////////////////////////////////////////////////////////////////////////////////

var nameRemap = (function (map) {
  var suffix_re = /-(\d+)?/;

  function increase_suffix(name) {
    var
    match = suffix_re.exec(name),
    index = match ? parseInt(match[1], 10) + 1 : 1;

    return name.replace(suffix_re, '') + '-' + index;
  }

  function allocate(model, name) {
    var
    prev_name = model.name.prev(),
    conflict  = map[name];

    if (model === map[prev_name]) {
      map[prev_name] = null;
    }

    map[name] = model;

    if (conflict && conflict !== model) {
      conflict.name(increase_suffix(name));
    }
  }

  function register(model) {
    allocate(model, model.name());

    model.name.subscribe(function (name) {
      allocate(model, name);
    });
  }

  return { register: register };
}({}));

var codeRemap = (function (map) {
  function find_free_code() {
    var code = N.config.app.autoguess_charcode.min;

    while (code <= N.config.app.autoguess_charcode.max) {
      if (!map[code]) {
        // got unused code
        return code;
      }

      // try next code
      code += 1;
    }

    // can't find empty code.
    // SHOULD NEVER happen in real life.
    throw "Run out of free codes";
  }

  function allocate(model, code) {
    var
    prev_code = model.code.prev(),
    conflict  = map[code];

    if (model === map[prev_code]) {
      map[prev_code] = null;
    }

    map[code] = model;

    if (conflict && conflict !== model) {
      conflict.code(prev_code === code ? find_free_code() : prev_code);
    }
  }

  function register(model) {
    if (map[model.code()]) {
      model.code(find_free_code());
    }

    model.code.subscribe(function (code) {
      allocate(model, code);
    });
  }

  return { register: register };
}({}));

function observableWithHistory(initial) {
  var curr, prev;

  prev = initial;
  curr = ko.observable(initial);

  curr.subscribe(function (value) {
    prev = value;
  }, curr, 'beforeChange');

  curr.prev = function () {
    return prev;
  };

  return curr;
}

////////////////////////////////////////////////////////////////////////////////

function GlyphModel(font, data) {

  //
  // Essential properties
  //

  this.uid              = data.uid;
  this.originalName     = data.css;
  this.originalCode     = data.code;

  //
  // Helper properties
  //

  this.font             = font;
  this.keywords         = (data.search || []).join(',');
  this.charRef          = fixedFromCharCode(glyphs_map[font.fontname][data.uid]);

  //
  // Actual properties state
  //

  this.selected         = ko.observable(false);
  this.name             = observableWithHistory(this.originalName);
  this.code             = observableWithHistory(this.originalCode);

  //
  // Helpers
  //

  this.toggleSelection  = function () {
    this.selected(!this.selected());
  }.bind(this);

  //
  // Complex class name based on selected state and css-ext data
  //

  this.displayClass     = ko.computed(function () {
    var list = [ data['css-ext'], this.selected() ? 'selected' : '' ];
    return _.filter(list, Boolean).join(' ');
  }, this);

  //
  // Whenever or not glyph should be treaten as "modified"
  //

  this.isModified       = ko.computed(function () {
    var value = false;

    // We need to call all "observable" properties to have proper deps graph
    value = !!this.selected() || value;
    value = this.name() !== this.originalName || value;
    value = this.code() !== this.originalCode || value;

    return value;
  }, this);

  //
  // code value as character (for code editor)
  //

  this.customChar       = ko.computed({
    read: function () {
      return fixedFromCharCode(this.code());
    },
    write: function (value) {
      this.code(fixedCharCodeAt(value));
    },
    owner: this
  });

  //
  // code value as hex-string (for code editor)
  //

  this.customHex        = ko.computed({
    read: function () {
      var code = this.code().toString(16).toUpperCase();
      return "0000".substr(0, Math.max(4 - code.length, 0)) + code;
    },
    write: function (value) {
      // value must be HEX string - omit invalid chars
      value = 0 + value.replace(/[^0-9a-fA-F]+/g, '');
      this.code(parseInt(value, 16));
    },
    owner: this
  });

  //
  // Register glyph in the names/codes swap-remap handler
  //

  nameRemap.register(this);
  codeRemap.register(this);
}

function FontModel(data) {

  //
  // Essential properties
  //

  this.id         = data.id;
  this.fullname   = data.font.fullname;
  this.fontname   = data.font.fontname;

  this.author     = data.meta.author;
  this.license    = data.meta.license;
  this.homepage   = data.meta.homepage;
  this.email      = data.meta.email;
  this.twitter    = data.meta.twitter;
  this.github     = data.meta.github;

  //
  // View state properties
  //

  this.collapsed  = ko.observable(false);

  //
  // Helpers
  //

  this.toggleCollapsed = function () {
    this.collapsed(!this.collapsed());
  }.bind(this);

  //
  // Array of font glyphs
  //

  this.glyphs     = _.map(data.glyphs, function (data) {
    return new GlyphModel(this, data);
  }, this);

  //
  // Returns array of selected glyphs of a font
  // throttling compensates mass reflows on multiselect
  //

  this.selectedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.glyphs, function (glyph) {
      if (glyph.selected()) {
        glyphs.push(glyph);
      }
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns array of modified glyphs of a font
  // throttling compensates mass reflows on multiselect
  //

  this.modifiedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.glyphs, function (glyph) {
      if (glyph.isModified()) {
        glyphs.push(glyph);
      }
    });

    return glyphs;
  }, this).extend({ throttle: 100 });
}

function FontsList() {
  this.fonts = _.map(embedded_fonts, function (data) {
    return new FontModel(data);
  });

  //
  // Returns array of selected glyphs from all fonts
  // throttling compensates mass reflows on multiselect
  //

  this.selectedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.fonts, function (font) {
      glyphs = glyphs.concat(font.selectedGlyphs());
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns amount of selected glyphs from all fonts
  //

  this.selectedCount = ko.computed(function () {
    return this.selectedGlyphs().length;
  }, this);

  //
  // Returns array of modified glyphs from all fonts
  // throttling compensates mass reflows on multiselect
  //

  this.modifiedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.fonts, function (font) {
      glyphs = glyphs.concat(font.modifiedGlyphs());
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns whenever font list has modified glyphs or collapsed fonts
  // throttling compensates mass reflows on multiselect
  //

  this.isModified = ko.computed(function () {
    var value = false;

    _.each(this.fonts, function (font) {
      // We need to call collapsed() of each font to have proper deps graph
      value = font.collapsed() || value;
    });

    // call modifiedGlyphs() in any case to have proper deps graph
    value = !!this.modifiedGlyphs().length || value;

    return value;
  }, this).extend({ throttle: 100 });
}

////////////////////////////////////////////////////////////////////////////////

var fontsList = new FontsList();
var fontSize  = ko.observable(16);

////////////////////////////////////////////////////////////////////////////////

N.on('font_size_change', fontSize);

var autoSaveSession = _.debounce(function () {
  var session = { fonts: {} };

  _.each(fontsList.fonts, function (font) {
    var font_data = { collapsed: font.collapsed(), glyphs: [] };

    _.each(font.glyphs, function (glyph) {
      if (glyph.isModified()) {
        font_data.glyphs.push({
          uid:        glyph.uid,
          selected:   glyph.selected(),
          orig_code:  glyph.originalCode,
          orig_css:   glyph.originalName,
          code:       glyph.code(),
          css:        glyph.name()
        });
      }
    });

    session.fonts[font.id] = font_data;
  });

  N.emit('session_save', session);
}, 500);

fontsList.isModified.subscribe(autoSaveSession);

N.on('session_load', function (session) {
  var fonts = session.fonts || [];

  _.each(fontsList.fonts, function (font) {
    if (!fonts[font.id]) {
      font.collapsed(false);
      return;
    }

    font.collapsed(!!fonts[font.id].collapsed);

    _.each(fonts[font.id].glyphs, function (glyph) {
      _.each(font.glyphs, function (g) {
        if (g.uid === glyph.uid) {
          g.selected(!!glyph.selected);
          g.code(glyph.code || glyph.orig_code || g.originalCode);
          g.name(glyph.css || glyph.orig_css || g.originalName);
        }
      });
    });
  });
});

N.on('reset_selected', function () {
  _.each(fontsList.selectedGlyphs(), function (glyph) {
    glyph.selected(false);
  });
});

N.on('reset_all', function () {
  _.each(fontsList.modifiedGlyphs(), function (glyph) {
    glyph.selected(false);
    glyph.code(glyph.originalCode);
    glyph.name(glyph.originalName);
  });
});

$(function () {
  var $view = $(render('app.selector')).appendTo('#selector');

  //
  // Bind model and view
  //

  ko.applyBindings({
    fonts:    fontsList.fonts,
    fontSize: fontSize
  }, $view.get(0));

  //
  // Init multi-select of glyphs
  //

  $view.selectable({
    filter: 'li.glyph:visible',
    distance: 5,
    stop: function () {
      var $els = $view.find('.glyph.ui-selected');

      // prevent from double-triggering event,
      // otherwise click event will be fired as well
      if (1 === $els.length) {
        return;
      }

      $els.each(function () {
        ko.dataFor(this).toggleSelection();
      });
    }
  });
});

////////////////////////////////////////////////////////////////////////////////

N.once('init_complete', function () {
  N.emit('fonts_ready', fontsList);
});
<commit_msg>Fix codes remapper<commit_after>/*global $, _, ko, N*/

'use strict';

////////////////////////////////////////////////////////////////////////////////

var
render          = require('../../../lib/render/client'),
embedded_fonts  = require('../../../lib/shared/embedded_fonts'),
glyphs_map      = require('../../../lib/shared/glyphs_map');

// Int to char, with fix for big numbers
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/fromCharCode
function fixedFromCharCode(code) {
  /*jshint bitwise: false*/
  if (code > 0xffff) {
    code -= 0x10000;
    var surrogate1 = 0xd800 + (code >> 10),
        surrogate2 = 0xdc00 + (code & 0x3ff);
    return String.fromCharCode(surrogate1, surrogate2);
  } else {
    return String.fromCharCode(code);
  }
}

// Char to Int, with fix for big numbers
function fixedCharCodeAt(char) {
  /*jshint bitwise: false*/
  var char1 = char.charCodeAt(0),
      char2 = char.charCodeAt(1);

  if ((char.length >= 2) &&
      ((char1 & 0xfc00) === 0xd800) &&
      ((char2 & 0xfc00) === 0xdc00)) {
    return 0x10000 + ((char1 - 0xd800) << 10) + (char2 - 0xdc00);
  } else {
    return char1;
  }
}

////////////////////////////////////////////////////////////////////////////////

var nameRemap = (function (map) {
  var suffix_re = /-(\d+)?/;

  function increase_suffix(name) {
    var
    match = suffix_re.exec(name),
    index = match ? parseInt(match[1], 10) + 1 : 1;

    return name.replace(suffix_re, '') + '-' + index;
  }

  function allocate(model, name) {
    var
    prev_name = model.name.prev(),
    conflict  = map[name];

    if (model === map[prev_name]) {
      map[prev_name] = null;
    }

    map[name] = model;

    if (conflict && conflict !== model) {
      conflict.name(increase_suffix(name));
    }
  }

  function register(model) {
    allocate(model, model.name());

    model.name.subscribe(function (name) {
      allocate(model, name);
    });
  }

  return { register: register };
}({}));

var codeRemap = (function (map) {
  function find_free_code() {
    var code = N.config.app.autoguess_charcode.min;

    while (code <= N.config.app.autoguess_charcode.max) {
      if (!map[code]) {
        // got unused code
        return code;
      }

      // try next code
      code++;
    }

    // SHOULD NEVER HAPPEN (only if max pool size is < amount of all glyphs):
    throw "Run out of free codes";
  }

  function allocate(model, code) {
    var
    prev_code = model.code.prev(),
    conflict  = map[code];

    if (model === map[prev_code]) {
      map[prev_code] = null;
    }

    map[code] = model;

    if (conflict && conflict !== model) {
      conflict.code(prev_code === code ? find_free_code() : prev_code);
    }
  }

  function register(model) {
    var code = model.code();

    // code is already taken
    if (map[code]) {
      model.code(code = find_free_code());
    }

    // register new model
    map[code] = model;

    model.code.subscribe(function (code) {
      allocate(model, code);
    });
  }

  return { register: register };
}({}));

function observableWithHistory(initial) {
  var curr, prev;

  prev = initial;
  curr = ko.observable(initial);

  curr.subscribe(function (value) {
    prev = value;
  }, curr, 'beforeChange');

  curr.prev = function () {
    return prev;
  };

  return curr;
}

////////////////////////////////////////////////////////////////////////////////

function GlyphModel(font, data) {

  //
  // Essential properties
  //

  this.uid              = data.uid;
  this.originalName     = data.css;
  this.originalCode     = data.code;

  //
  // Helper properties
  //

  this.font             = font;
  this.keywords         = (data.search || []).join(',');
  this.charRef          = fixedFromCharCode(glyphs_map[font.fontname][data.uid]);

  //
  // Actual properties state
  //

  this.selected         = ko.observable(false);
  this.name             = observableWithHistory(this.originalName);
  this.code             = observableWithHistory(this.originalCode);

  //
  // Helpers
  //

  this.toggleSelection  = function () {
    this.selected(!this.selected());
  }.bind(this);

  //
  // Complex class name based on selected state and css-ext data
  //

  this.displayClass     = ko.computed(function () {
    var list = [ data['css-ext'], this.selected() ? 'selected' : '' ];
    return _.filter(list, Boolean).join(' ');
  }, this);

  //
  // Whenever or not glyph should be treaten as "modified"
  //

  this.isModified       = ko.computed(function () {
    var value = false;

    // We need to call all "observable" properties to have proper deps graph
    value = !!this.selected() || value;
    value = this.name() !== this.originalName || value;
    value = this.code() !== this.originalCode || value;

    return value;
  }, this);

  //
  // code value as character (for code editor)
  //

  this.customChar       = ko.computed({
    read: function () {
      return fixedFromCharCode(this.code());
    },
    write: function (value) {
      this.code(fixedCharCodeAt(value));
    },
    owner: this
  });

  //
  // code value as hex-string (for code editor)
  //

  this.customHex        = ko.computed({
    read: function () {
      var code = this.code().toString(16).toUpperCase();
      return "0000".substr(0, Math.max(4 - code.length, 0)) + code;
    },
    write: function (value) {
      // value must be HEX string - omit invalid chars
      value = 0 + value.replace(/[^0-9a-fA-F]+/g, '');
      this.code(parseInt(value, 16));
    },
    owner: this
  });

  //
  // Register glyph in the names/codes swap-remap handler
  //

  nameRemap.register(this);
  codeRemap.register(this);
}

function FontModel(data) {

  //
  // Essential properties
  //

  this.id         = data.id;
  this.fullname   = data.font.fullname;
  this.fontname   = data.font.fontname;

  this.author     = data.meta.author;
  this.license    = data.meta.license;
  this.homepage   = data.meta.homepage;
  this.email      = data.meta.email;
  this.twitter    = data.meta.twitter;
  this.github     = data.meta.github;

  //
  // View state properties
  //

  this.collapsed  = ko.observable(false);

  //
  // Helpers
  //

  this.toggleCollapsed = function () {
    this.collapsed(!this.collapsed());
  }.bind(this);

  //
  // Array of font glyphs
  //

  this.glyphs     = _.map(data.glyphs, function (data) {
    return new GlyphModel(this, data);
  }, this);

  //
  // Returns array of selected glyphs of a font
  // throttling compensates mass reflows on multiselect
  //

  this.selectedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.glyphs, function (glyph) {
      if (glyph.selected()) {
        glyphs.push(glyph);
      }
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns array of modified glyphs of a font
  // throttling compensates mass reflows on multiselect
  //

  this.modifiedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.glyphs, function (glyph) {
      if (glyph.isModified()) {
        glyphs.push(glyph);
      }
    });

    return glyphs;
  }, this).extend({ throttle: 100 });
}

function FontsList() {
  this.fonts = _.map(embedded_fonts, function (data) {
    return new FontModel(data);
  });

  //
  // Returns array of selected glyphs from all fonts
  // throttling compensates mass reflows on multiselect
  //

  this.selectedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.fonts, function (font) {
      glyphs = glyphs.concat(font.selectedGlyphs());
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns amount of selected glyphs from all fonts
  //

  this.selectedCount = ko.computed(function () {
    return this.selectedGlyphs().length;
  }, this);

  //
  // Returns array of modified glyphs from all fonts
  // throttling compensates mass reflows on multiselect
  //

  this.modifiedGlyphs = ko.computed(function () {
    var glyphs = [];

    _.each(this.fonts, function (font) {
      glyphs = glyphs.concat(font.modifiedGlyphs());
    });

    return glyphs;
  }, this).extend({ throttle: 100 });

  //
  // Returns whenever font list has modified glyphs or collapsed fonts
  // throttling compensates mass reflows on multiselect
  //

  this.isModified = ko.computed(function () {
    var value = false;

    _.each(this.fonts, function (font) {
      // We need to call collapsed() of each font to have proper deps graph
      value = font.collapsed() || value;
    });

    // call modifiedGlyphs() in any case to have proper deps graph
    value = !!this.modifiedGlyphs().length || value;

    return value;
  }, this).extend({ throttle: 100 });
}

////////////////////////////////////////////////////////////////////////////////

var fontsList = new FontsList();
var fontSize  = ko.observable(16);

////////////////////////////////////////////////////////////////////////////////

N.on('font_size_change', fontSize);

var autoSaveSession = _.debounce(function () {
  var session = { fonts: {} };

  _.each(fontsList.fonts, function (font) {
    var font_data = { collapsed: font.collapsed(), glyphs: [] };

    _.each(font.glyphs, function (glyph) {
      if (glyph.isModified()) {
        font_data.glyphs.push({
          uid:        glyph.uid,
          selected:   glyph.selected(),
          orig_code:  glyph.originalCode,
          orig_css:   glyph.originalName,
          code:       glyph.code(),
          css:        glyph.name()
        });
      }
    });

    session.fonts[font.id] = font_data;
  });

  N.emit('session_save', session);
}, 500);

fontsList.isModified.subscribe(autoSaveSession);

N.on('session_load', function (session) {
  var fonts = session.fonts || [];

  _.each(fontsList.fonts, function (font) {
    if (!fonts[font.id]) {
      font.collapsed(false);
      return;
    }

    font.collapsed(!!fonts[font.id].collapsed);

    _.each(fonts[font.id].glyphs, function (glyph) {
      _.each(font.glyphs, function (g) {
        if (g.uid === glyph.uid) {
          g.selected(!!glyph.selected);
          g.code(glyph.code || glyph.orig_code || g.originalCode);
          g.name(glyph.css || glyph.orig_css || g.originalName);
        }
      });
    });
  });
});

N.on('reset_selected', function () {
  _.each(fontsList.selectedGlyphs(), function (glyph) {
    glyph.selected(false);
  });
});

N.on('reset_all', function () {
  _.each(fontsList.modifiedGlyphs(), function (glyph) {
    glyph.selected(false);
    glyph.code(glyph.originalCode);
    glyph.name(glyph.originalName);
  });
});

$(function () {
  var $view = $(render('app.selector')).appendTo('#selector');

  //
  // Bind model and view
  //

  ko.applyBindings({
    fonts:    fontsList.fonts,
    fontSize: fontSize
  }, $view.get(0));

  //
  // Init multi-select of glyphs
  //

  $view.selectable({
    filter: 'li.glyph:visible',
    distance: 5,
    stop: function () {
      var $els = $view.find('.glyph.ui-selected');

      // prevent from double-triggering event,
      // otherwise click event will be fired as well
      if (1 === $els.length) {
        return;
      }

      $els.each(function () {
        ko.dataFor(this).toggleSelection();
      });
    }
  });
});

////////////////////////////////////////////////////////////////////////////////

N.once('init_complete', function () {
  N.emit('fonts_ready', fontsList);
});
<|endoftext|>


<commit_before>// Generated using generator-angular
'use strict';

module.exports = function(grunt) {
  /*jshint camelcase: false */

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);
  require('grunt-connect-proxy')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  var serveStatic = require('serve-static');

  //TODO Mandantenfähigkeit fehlt (siehe var config = ...)
  var env = 'dev';
  if (grunt.option('env') !== null && grunt.option('env') !== undefined) {
    env = grunt.option('env');
  }

  // text replace in js files used for environment specific configurations
  var config = {
    BUILD_NR: grunt.option('buildnr') || 'dev',
  };

  // Define the configuration for all the tasks
  grunt.initConfig({
    // Project settings
    openolitor: {
      // configurable paths
      app: require('./bower.json').appPath || 'app',
      dist: 'dist'
    },

    // task used to replace config values in js files
    replace: {
      options: {
        patterns: [
          {
            json: config
          }
        ]
      },
      dev: {
        files: [
          {
            expand: true,
            flatten: true,
            src: ['<%= openolitor.app %>/scripts/app.js'],
            dest: '.tmp/scripts'
          }
        ]
      },
      prod: {
        files: [
          {
            expand: true,
            flatten: true,
            src: ['.tmp/concat/scripts/scripts.js'],
            dest: '.tmp/concat/scripts'
          }
        ]
      }
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: [
          '<%= openolitor.app %>/scripts/**/*.js',
          '<%= openolitor.app %>/bower_components/openolitor-core/scripts/**/*.js'
        ],
        tasks: ['newer:jshint:all', 'replace:dev'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      sass: {
        files: ['<%= openolitor.app %>/styles/**/*.{scss,sass}'],
        tasks: ['sass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= openolitor.app %>/scripts/**/*.html',
          '<%= openolitor.app %>/index.html',
          '.tmp/styles/**/*.css',
          '<%= openolitor.app %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      proxies: [
        {
          context: '/api-',
          host: 'localhost',
          port: 9003,
          https: false,
          xforward: false,
          rewrite: {
            '^/api-(.*)$': '/$1',
            '^/api-(.*/ws)$': '/$1'
          }
        }
      ],
      options: {
        port: 9001,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          base: ['.tmp', '<%= openolitor.app %>'],
          middleware: function (connect, options) {
              if (!Array.isArray(options.base)) {
                  options.base = [options.base];
              }

              // Setup the proxy
              var middlewares = [require('grunt-connect-proxy/lib/utils').proxyRequest];

              // Serve static files.
              options.base.forEach(function(base) {
                  middlewares.push(serveStatic(base));
              });

              return middlewares;
          }
        }
      },
      test: {
        options: {
          port: 9001,
          base: ['.tmp', 'test', '<%= openolitor.app %>']
        }
      },
      dist: {
        options: {
          base: '<%= openolitor.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        '<%= openolitor.app %>/scripts/{,*/}*.js',
        '!<%= openolitor.app %>/scripts/i18n/translations.js'
      ],
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [
          {
            dot: true,
            src: [
              '.tmp',
              '<%= openolitor.dist %>/*',
              '!<%= openolitor.dist %>/.git*',
              '!<%= openolitor.dist %>/index.php'
            ]
          }
        ]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [
          {
            expand: true,
            cwd: '.tmp/styles/',
            src: '**/*.css',
            dest: '.tmp/styles/'
          }
        ]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      options: {
        //        cwd: '<%= openolitor.app %>'
      },
      app: {
        src: ['<%= openolitor.app %>/index.html'],
        exclude: ['bower_components/bootstrap-sass-official/*'],
        ignorePath: '<%= openolitor.app %>/'
      },
      sass: {
        src: ['<%= openolitor.app %>/styles/**/*.{scss,sass}'],
        ignorePath: '<%= openolitor.app %>/bower_components/'
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    sass: {
      options: {
        includePaths: ['app/bower_components']
      },
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/styles',
            src: ['*.scss'],
            dest: '.tmp/styles',
            ext: '.css'
          }
        ]
      },
      server: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/styles',
            src: ['*.scss'],
            dest: '.tmp/styles',
            ext: '.css'
          }
        ]
      }
    },

    uglify: {
      options: {
        exceptionsFiles: [ 'uglifyExceptions.json' ]
      }
    },

    // Renames files for browser caching purposes
    rev: {
      dist: {
        files: {
          src: [
            '<%= openolitor.dist %>/scripts/**/*.js',
            '<%= openolitor.dist %>/styles/**/*.css',
            '<%= openolitor.dist %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
            '<%= openolitor.dist %>/styles/fonts/**/*',
          ]
        }
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= openolitor.app %>/index.html',
      options: {
        dest: '<%= openolitor.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= openolitor.dist %>/**/*.html'],
      css: ['<%= openolitor.dist %>/styles/**/*.css'],
      options: {
        assetsDirs: ['<%= openolitor.dist %>', '<%= openolitor.dist %>/images']
      }
    },

    // The following *-min tasks produce minified files in the dist folder
    cssmin: {
      options: {
        root: '<%= openolitor.app %>'
      }
    },

    imagemin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/images',
            src: '**/*.{jpg,jpeg,gif}', // png doesn't work on buildserver
            dest: '<%= openolitor.dist %>/images'
          }
        ]
      }
    },

    svgmin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/images',
            src: '{,*/}*.svg',
            dest: '<%= openolitor.dist %>/images'
          }
        ]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: false,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.dist %>',
            src: ['*.html', 'scripts/**/*.html'],
            dest: '<%= openolitor.dist %>'
          }
        ]
      }
    },

    // ngAnnotate tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngAnnotate: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '.tmp/concat/scripts',
            src: '*.js',
            dest: '.tmp/concat/scripts'
          }
        ]
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [
          {
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>',
            dest: '<%= openolitor.dist %>',
            src: [
              '*.{ico,png,txt}',
              '.htaccess',
              '*.html',
              'scripts/**/*.html',
              'images/**/*.{webp,png,jpg}',
              'third_party/**',
              'fonts/**/*'
            ]
          },
          {
            expand: true,
            cwd: '<%= openolitor.app %>',
            dest: '<%= openolitor.dist %>',
            src: ['environments/*']
          },
          {
            expand: true,
            cwd: '.',
            dest: '<%= openolitor.dist %>',
            src: ['nginx.conf']
          },
          {
            expand: true,
            cwd: '.tmp/images',
            dest: '<%= openolitor.dist %>/images',
            src: ['generated/*']
          },
          {
            //for bootstrap fonts, maybe we could use the scss/less where the correct font url path will be inserted
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>/bower_components/bootstrap/dist',
            src: ['fonts/*.*'],
            dest: '<%= openolitor.dist %>'
          },
          {
            //for font-awesome
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>/bower_components/font-awsome',
            src: ['fonts/*.*'],
            dest: '<%= openolitor.dist %>'
          }
        ]
      },
      styles: {
        expand: true,
        cwd: '<%= openolitor.app %>/styles',
        dest: '.tmp/styles/',
        src: '**/*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: ['sass:server', 'copy:styles'],
      test: ['copy:styles'],
      dist: ['sass', 'copy:styles', 'imagemin', 'svgmin']
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      },
      live: {
        configFile: 'test/karma.conf.js',
        singleRun: false,
        autoWatch: true
      },
      continuous: {
        configFile: 'test/karma.conf.js',
        singleRun: 'true',
        browsers: ['PhantomJS'],
        reporters: ['dots', 'junit', 'coverage'],
        junitReporter: {
          outputFile: 'test/test-results.xml'
        },
        coverageReporter: {
          reporters: [
            {
              type: 'lcov',
              dir: 'test/coverage/reports'
            },
            {
              type: 'text',
              dir: 'test/coverage/reports'
            }
          ]
        },
        preprocessors: {
          // source files, that you wanna generate coverage for
          // do not include tests or libraries
          // (these files will be instrumented by Istanbul)
          'app/scripts/**/*.js': ['coverage'],
          'app/scripts/**/*.html': ['ng-html2js']
        }
      }
    },

    protractor: {
      options: {
        configFile: 'test/protractor.conf.js', // Default config file
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false, // If true, protractor will not use colors in its output.
        args: {
          // Arguments passed to the command
        }
      },
      all: {} // Grunt requires at least one target to run so you can simply put 'all: {}' here too.
    },

    protractor_webdriver: {
      default: {
        options: {
          command: 'custom-webdriver-manager start --standalone'
        }
      }
    },

    // i18n
    nggettext_extract: {
      pot: {
        files: {
          'i18n/template.pot': [
            '<%= openolitor.app %>/**/*.html',
            '<%= openolitor.app %>/**/*.js',
            '!<%= openolitor.app %>/bower_components/**'
          ]
        }
      }
    },

    nggettext_compile: {
      all: {
        files: {
          '<%= openolitor.app %>/scripts/i18n/translations.js': ['i18n/*/*.po']
        }
      }
    }
  });

  grunt.registerTask('serve', function(target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'configureProxies:server',
      'connect:livereload',
      'replace:dev',
      'watch'
    ]);
  });

  grunt.registerTask('server', function(target) {
    grunt.log.warn(
      'The `server` task has been deprecated. Use `grunt serve` to start a server.'
    );
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('core-test', [
    'newer:jshint',
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test'
  ]);

  grunt.registerTask('test', ['core-test', 'karma:unit']);

  grunt.registerTask('~test', ['core-test', 'karma:live']);

  grunt.registerTask('citest', ['clean', 'core-test', 'karma:continuous']);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'replace:prod',
    'ngAnnotate',
    'copy:dist',
    'cssmin',
    'uglify',
    'rev',
    'usemin',
    'htmlmin',
  ]);

  grunt.registerTask('i18nextract', ['nggettext_extract']);

  grunt.registerTask('default', ['citest', 'build']);
};
<commit_msg>OO enable web socket in the grunt proxy<commit_after>// Generated using generator-angular
'use strict';

module.exports = function(grunt) {
  /*jshint camelcase: false */

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);
  require('grunt-connect-proxy')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  var serveStatic = require('serve-static');

  //TODO Mandantenfähigkeit fehlt (siehe var config = ...)
  var env = 'dev';
  if (grunt.option('env') !== null && grunt.option('env') !== undefined) {
    env = grunt.option('env');
  }

  // text replace in js files used for environment specific configurations
  var config = {
    BUILD_NR: grunt.option('buildnr') || 'dev',
  };

  // Define the configuration for all the tasks
  grunt.initConfig({
    // Project settings
    openolitor: {
      // configurable paths
      app: require('./bower.json').appPath || 'app',
      dist: 'dist'
    },

    // task used to replace config values in js files
    replace: {
      options: {
        patterns: [
          {
            json: config
          }
        ]
      },
      dev: {
        files: [
          {
            expand: true,
            flatten: true,
            src: ['<%= openolitor.app %>/scripts/app.js'],
            dest: '.tmp/scripts'
          }
        ]
      },
      prod: {
        files: [
          {
            expand: true,
            flatten: true,
            src: ['.tmp/concat/scripts/scripts.js'],
            dest: '.tmp/concat/scripts'
          }
        ]
      }
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: [
          '<%= openolitor.app %>/scripts/**/*.js',
          '<%= openolitor.app %>/bower_components/openolitor-core/scripts/**/*.js'
        ],
        tasks: ['newer:jshint:all', 'replace:dev'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      sass: {
        files: ['<%= openolitor.app %>/styles/**/*.{scss,sass}'],
        tasks: ['sass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= openolitor.app %>/scripts/**/*.html',
          '<%= openolitor.app %>/index.html',
          '.tmp/styles/**/*.css',
          '<%= openolitor.app %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      proxies: [
        {
          context: '/api-',
          host: 'localhost',
          port: 9003,
          https: false,
          xforward: false,
          ws: true,
          rewrite: {
            '^/api-(.*)$': '/$1',
            '^/api-(.*/ws)$': '/$1'
          }
        }
      ],
      options: {
        port: 9001,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          base: ['.tmp', '<%= openolitor.app %>'],
          middleware: function (connect, options) {
              if (!Array.isArray(options.base)) {
                  options.base = [options.base];
              }

              // Setup the proxy
              var middlewares = [require('grunt-connect-proxy/lib/utils').proxyRequest];

              // Serve static files.
              options.base.forEach(function(base) {
                  middlewares.push(serveStatic(base));
              });

              return middlewares;
          }
        }
      },
      test: {
        options: {
          port: 9001,
          base: ['.tmp', 'test', '<%= openolitor.app %>']
        }
      },
      dist: {
        options: {
          base: '<%= openolitor.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        '<%= openolitor.app %>/scripts/{,*/}*.js',
        '!<%= openolitor.app %>/scripts/i18n/translations.js'
      ],
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [
          {
            dot: true,
            src: [
              '.tmp',
              '<%= openolitor.dist %>/*',
              '!<%= openolitor.dist %>/.git*',
              '!<%= openolitor.dist %>/index.php'
            ]
          }
        ]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [
          {
            expand: true,
            cwd: '.tmp/styles/',
            src: '**/*.css',
            dest: '.tmp/styles/'
          }
        ]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      options: {
        //        cwd: '<%= openolitor.app %>'
      },
      app: {
        src: ['<%= openolitor.app %>/index.html'],
        exclude: ['bower_components/bootstrap-sass-official/*'],
        ignorePath: '<%= openolitor.app %>/'
      },
      sass: {
        src: ['<%= openolitor.app %>/styles/**/*.{scss,sass}'],
        ignorePath: '<%= openolitor.app %>/bower_components/'
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    sass: {
      options: {
        includePaths: ['app/bower_components']
      },
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/styles',
            src: ['*.scss'],
            dest: '.tmp/styles',
            ext: '.css'
          }
        ]
      },
      server: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/styles',
            src: ['*.scss'],
            dest: '.tmp/styles',
            ext: '.css'
          }
        ]
      }
    },

    uglify: {
      options: {
        exceptionsFiles: [ 'uglifyExceptions.json' ]
      }
    },

    // Renames files for browser caching purposes
    rev: {
      dist: {
        files: {
          src: [
            '<%= openolitor.dist %>/scripts/**/*.js',
            '<%= openolitor.dist %>/styles/**/*.css',
            '<%= openolitor.dist %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
            '<%= openolitor.dist %>/styles/fonts/**/*',
          ]
        }
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= openolitor.app %>/index.html',
      options: {
        dest: '<%= openolitor.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= openolitor.dist %>/**/*.html'],
      css: ['<%= openolitor.dist %>/styles/**/*.css'],
      options: {
        assetsDirs: ['<%= openolitor.dist %>', '<%= openolitor.dist %>/images']
      }
    },

    // The following *-min tasks produce minified files in the dist folder
    cssmin: {
      options: {
        root: '<%= openolitor.app %>'
      }
    },

    imagemin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/images',
            src: '**/*.{jpg,jpeg,gif}', // png doesn't work on buildserver
            dest: '<%= openolitor.dist %>/images'
          }
        ]
      }
    },

    svgmin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.app %>/images',
            src: '{,*/}*.svg',
            dest: '<%= openolitor.dist %>/images'
          }
        ]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: false,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [
          {
            expand: true,
            cwd: '<%= openolitor.dist %>',
            src: ['*.html', 'scripts/**/*.html'],
            dest: '<%= openolitor.dist %>'
          }
        ]
      }
    },

    // ngAnnotate tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngAnnotate: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '.tmp/concat/scripts',
            src: '*.js',
            dest: '.tmp/concat/scripts'
          }
        ]
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [
          {
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>',
            dest: '<%= openolitor.dist %>',
            src: [
              '*.{ico,png,txt}',
              '.htaccess',
              '*.html',
              'scripts/**/*.html',
              'images/**/*.{webp,png,jpg}',
              'third_party/**',
              'fonts/**/*'
            ]
          },
          {
            expand: true,
            cwd: '<%= openolitor.app %>',
            dest: '<%= openolitor.dist %>',
            src: ['environments/*']
          },
          {
            expand: true,
            cwd: '.',
            dest: '<%= openolitor.dist %>',
            src: ['nginx.conf']
          },
          {
            expand: true,
            cwd: '.tmp/images',
            dest: '<%= openolitor.dist %>/images',
            src: ['generated/*']
          },
          {
            //for bootstrap fonts, maybe we could use the scss/less where the correct font url path will be inserted
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>/bower_components/bootstrap/dist',
            src: ['fonts/*.*'],
            dest: '<%= openolitor.dist %>'
          },
          {
            //for font-awesome
            expand: true,
            dot: true,
            cwd: '<%= openolitor.app %>/bower_components/font-awsome',
            src: ['fonts/*.*'],
            dest: '<%= openolitor.dist %>'
          }
        ]
      },
      styles: {
        expand: true,
        cwd: '<%= openolitor.app %>/styles',
        dest: '.tmp/styles/',
        src: '**/*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: ['sass:server', 'copy:styles'],
      test: ['copy:styles'],
      dist: ['sass', 'copy:styles', 'imagemin', 'svgmin']
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      },
      live: {
        configFile: 'test/karma.conf.js',
        singleRun: false,
        autoWatch: true
      },
      continuous: {
        configFile: 'test/karma.conf.js',
        singleRun: 'true',
        browsers: ['PhantomJS'],
        reporters: ['dots', 'junit', 'coverage'],
        junitReporter: {
          outputFile: 'test/test-results.xml'
        },
        coverageReporter: {
          reporters: [
            {
              type: 'lcov',
              dir: 'test/coverage/reports'
            },
            {
              type: 'text',
              dir: 'test/coverage/reports'
            }
          ]
        },
        preprocessors: {
          // source files, that you wanna generate coverage for
          // do not include tests or libraries
          // (these files will be instrumented by Istanbul)
          'app/scripts/**/*.js': ['coverage'],
          'app/scripts/**/*.html': ['ng-html2js']
        }
      }
    },

    protractor: {
      options: {
        configFile: 'test/protractor.conf.js', // Default config file
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false, // If true, protractor will not use colors in its output.
        args: {
          // Arguments passed to the command
        }
      },
      all: {} // Grunt requires at least one target to run so you can simply put 'all: {}' here too.
    },

    protractor_webdriver: {
      default: {
        options: {
          command: 'custom-webdriver-manager start --standalone'
        }
      }
    },

    // i18n
    nggettext_extract: {
      pot: {
        files: {
          'i18n/template.pot': [
            '<%= openolitor.app %>/**/*.html',
            '<%= openolitor.app %>/**/*.js',
            '!<%= openolitor.app %>/bower_components/**'
          ]
        }
      }
    },

    nggettext_compile: {
      all: {
        files: {
          '<%= openolitor.app %>/scripts/i18n/translations.js': ['i18n/*/*.po']
        }
      }
    }
  });

  grunt.registerTask('serve', function(target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'configureProxies:server',
      'connect:livereload',
      'replace:dev',
      'watch'
    ]);
  });

  grunt.registerTask('server', function(target) {
    grunt.log.warn(
      'The `server` task has been deprecated. Use `grunt serve` to start a server.'
    );
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('core-test', [
    'newer:jshint',
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test'
  ]);

  grunt.registerTask('test', ['core-test', 'karma:unit']);

  grunt.registerTask('~test', ['core-test', 'karma:live']);

  grunt.registerTask('citest', ['clean', 'core-test', 'karma:continuous']);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'replace:prod',
    'ngAnnotate',
    'copy:dist',
    'cssmin',
    'uglify',
    'rev',
    'usemin',
    'htmlmin',
  ]);

  grunt.registerTask('i18nextract', ['nggettext_extract']);

  grunt.registerTask('default', ['citest', 'build']);
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import withStyles from '../styles/withStyles';
import Fade from '../Fade';

export const styles = {
  /* Styles applied to the root element. */
  root: {
    zIndex: -1,
    position: 'fixed',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    right: 0,
    bottom: 0,
    top: 0,
    left: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    WebkitTapHighlightColor: 'transparent',
    // Disable scroll capabilities.
    touchAction: 'none',
  },
  /* Styles applied to the root element if `invisible={true}`. */
  invisible: {
    backgroundColor: 'transparent',
  },
};

const Backdrop = React.forwardRef(function Backdrop(props, ref) {
  const {
    children,
    classes,
    className,
    invisible = false,
    open,
    transitionDuration,
    ...other
  } = props;

  return (
    <Fade in={open} timeout={transitionDuration} {...other}>
      <div
        data-mui-test="Backdrop"
        className={clsx(
          classes.root,
          {
            [classes.invisible]: invisible,
          },
          className,
        )}
        aria-hidden
        ref={ref}
      >
        {children}
      </div>
    </Fade>
  );
});

Backdrop.propTypes = {
  // ----------------------------- Warning --------------------------------
  // | These PropTypes are generated from the TypeScript type definitions |
  // |     To update them edit the d.ts file and run "yarn proptypes"     |
  // ----------------------------------------------------------------------
  /**
   * The content of the component.
   */
  children: PropTypes.node,
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css) below for more details.
   */
  classes: PropTypes.object,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * If `true`, the backdrop is invisible.
   * It can be used when rendering a popover or a custom select component.
   */
  invisible: PropTypes.bool,
  /**
   * If `true`, the backdrop is open.
   */
  open: PropTypes.bool.isRequired,
  /**
   * The duration for the transition, in milliseconds.
   * You may specify a single timeout for all transitions, or individually with an object.
   */
  transitionDuration: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.shape({
      appear: PropTypes.number,
      enter: PropTypes.number,
      exit: PropTypes.number,
    }),
  ]),
};

export default withStyles(styles, { name: 'MuiBackdrop' })(Backdrop);
<commit_msg>[Backdrop] Comment on z-index use case (#18589)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import withStyles from '../styles/withStyles';
import Fade from '../Fade';

export const styles = {
  /* Styles applied to the root element. */
  root: {
    // Improve scrollable dialog support.
    zIndex: -1,
    position: 'fixed',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    right: 0,
    bottom: 0,
    top: 0,
    left: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    WebkitTapHighlightColor: 'transparent',
    // Disable scroll capabilities.
    touchAction: 'none',
  },
  /* Styles applied to the root element if `invisible={true}`. */
  invisible: {
    backgroundColor: 'transparent',
  },
};

const Backdrop = React.forwardRef(function Backdrop(props, ref) {
  const {
    children,
    classes,
    className,
    invisible = false,
    open,
    transitionDuration,
    ...other
  } = props;

  return (
    <Fade in={open} timeout={transitionDuration} {...other}>
      <div
        data-mui-test="Backdrop"
        className={clsx(
          classes.root,
          {
            [classes.invisible]: invisible,
          },
          className,
        )}
        aria-hidden
        ref={ref}
      >
        {children}
      </div>
    </Fade>
  );
});

Backdrop.propTypes = {
  // ----------------------------- Warning --------------------------------
  // | These PropTypes are generated from the TypeScript type definitions |
  // |     To update them edit the d.ts file and run "yarn proptypes"     |
  // ----------------------------------------------------------------------
  /**
   * The content of the component.
   */
  children: PropTypes.node,
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css) below for more details.
   */
  classes: PropTypes.object,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * If `true`, the backdrop is invisible.
   * It can be used when rendering a popover or a custom select component.
   */
  invisible: PropTypes.bool,
  /**
   * If `true`, the backdrop is open.
   */
  open: PropTypes.bool.isRequired,
  /**
   * The duration for the transition, in milliseconds.
   * You may specify a single timeout for all transitions, or individually with an object.
   */
  transitionDuration: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.shape({
      appear: PropTypes.number,
      enter: PropTypes.number,
      exit: PropTypes.number,
    }),
  ]),
};

export default withStyles(styles, { name: 'MuiBackdrop' })(Backdrop);
<|endoftext|>


<commit_before>module.exports = function (grunt)
{
	grunt.loadNpmTasks('grunt-contrib-uglify');

	grunt.initConfig({
		pkg : grunt.file.readJSON('package.json'),
		uglify :
		{
			options :
			{
				banner : '/*! <%= pkg.name %> | Copyright (C) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> | <%= pkg.homepage %> | Released under the MIT License */\n'
			},
			dist :
			{
				files :
				{
					'jquery.thar.min.js': ['jquery.thar.js']
				}
			}
		}
	});

	grunt.registerTask('default', ['uglify']);
};<commit_msg>Create an additional file with version number attach to the name.<commit_after>module.exports = function (grunt)
{
	grunt.loadNpmTasks('grunt-contrib-uglify');

	grunt.initConfig({
		pkg : grunt.file.readJSON('package.json'),
		uglify :
		{
			options :
			{
				banner : '/*! <%= pkg.name %> | Copyright (C) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> | <%= pkg.homepage %> | Released under the MIT License */\n'
			},
			dist :
			{
				files :
				{
					'jquery.thar.min.js': ['jquery.thar.js'],
					'jquery.thar.v<%= pkg.version %>.min.js': ['jquery.thar.js']
				}
			}
		}
	});

	grunt.registerTask('default', ['uglify']);
};<|endoftext|>


<commit_before>'use strict'
var Observable = require('vigour-js/lib/observable')
// Observable.prototype.inject(require('vigour-js/li'))
var colors = require('colors-browserify') //eslint-disable-line
var Hub = require('../../lib')

// var server = global.server = new Hub({
//   key: 'server',
//   adapter: {
//     id: 'randomserver',
//     inject: require('../../lib/protocol/mock/'),
//     mock: {
//       server: 'testserver'
//     }
//   }
// })

// var server2 = global.server2 = new Hub({
//   key: 'server2',
//   adapter: {
//     id: 'randomserver2',
//     inject: require('../../lib/protocol/mock/'),
//     mock: {
//       // val:'testserver',
//       server: 'testserver2'
//     }
//   }
// })

// server.get('time', {})
// server2.get('time', {})

var client = global.client = new Hub({
  adapter: {
    id: 'testclient',
    inject: require('../../lib/protocol/websocket'),
    // inject: require('../../lib/protocol/mock'),
    websocket: 'ws://localhost:3032'
    // mock: 'testserver2'
  }
})

// var client2 = global.client2 = new Hub({
//   adapter: {
//     id: 'testclient2',
//     inject: require('../../lib/protocol/mock'),
//     // val: 'ws://localhost:3031'
//     mock: 'testserver2'
//     // scope: 'murder'
//   }
// })

// client2.adapter.scope.val = 'murder'

// client.get('time', {})
// client2.get('time', {})

// client.adapter.mock.connected.is(true, function () {
  // console.log('lullllzzzzzz'.rainbow)
  // setTimeout(() => {
  //   // console.clear()
  //   server.set({
  //     time: 222
  //   })
  // }, 100)
// })

// client.subscribe({
//   time: true
// }, function (data, event) {
//   for (var i in data) {
//     // here we want the real data!
//     console.log('hello', data[i].origin.path.join('.'), data[i].data, event)
//     // console.log(data[i].origin._on.data.getBound(event, this).data)
//   }
// })
//
// client.time.on(function (data) {
//   console.log('mystery ballz', data)
// })
//
// client2.time.on(function (data) {
//   console.log('mystery ballz -- 2', data)
//   // client2.set({
//   //   time: 88888888
//   // })
// })

console.line = false

// setTimeout(function () {
  // console.clear()
  // client2.subscribe({
  //   time: true
  //   // flurps: true
  // }, function (data, event) {
  //   for (var i in data) {
  //     // here we want the real data!
  //     console.log('hello', data[i].origin.path.join('.'), data[i].data, event)
  //     // console.log(data[i].origin._on.data.getBound(event, this).data)
  //   }
  // })

  // console.log('ok expect 222 myster ballz 2 to fire')
// }, 2000)

// client.set({flurps: true})

var o = new Observable({
  x: true
})

o.x.subscribe({
  $upward: {
    time: true
  }
}, function (data, event) {
  console.log('unicorns'.rainbow, event, data)
})

setTimeout(() => {
  console.clear()
  // client.subscribe({
  //   time: true
  // }, function () {
  //   console.log('unicorns'.rainbow)
  // })
  o.val = client
}, 500)

var Element = require('vigour-element')
var App = require('vigour-element/lib/app')

Element.prototype.inject(
  require('vigour-js/lib/operator/subscribe'),
  require('vigour-element/lib/property/text'),
  require('vigour-element/lib/property/css'),
  require('vigour-element/lib/property/style')
)

var Property = require('vigour-element/lib/property')
Property.prototype.inject(
  require('vigour-js/lib/operator/subscribe')
)

setTimeout(function() {
  var app = new App({
    node: document.body,
    style:{
      border:'10px solid red'
    },
    james: {
      node: 'input',
      text: { $: 'time' },
      on: {
        keyup () {
          this.text.origin.val = this.node.value
        }
      }
    }
  })

  app.val = client
// app.youzi.val = client2
}, 500)
<commit_msg>formatting<commit_after>'use strict'
var Observable = require('vigour-js/lib/observable')
// Observable.prototype.inject(require('vigour-js/li'))
var colors = require('colors-browserify') //eslint-disable-line
var Hub = require('../../lib')

// var server = global.server = new Hub({
//   key: 'server',
//   adapter: {
//     id: 'randomserver',
//     inject: require('../../lib/protocol/mock/'),
//     mock: {
//       server: 'testserver'
//     }
//   }
// })

// var server2 = global.server2 = new Hub({
//   key: 'server2',
//   adapter: {
//     id: 'randomserver2',
//     inject: require('../../lib/protocol/mock/'),
//     mock: {
//       // val:'testserver',
//       server: 'testserver2'
//     }
//   }
// })

// server.get('time', {})
// server2.get('time', {})

var client = global.client = new Hub({
  adapter: {
    id: 'testclient',
    inject: require('../../lib/protocol/websocket'),
    // inject: require('../../lib/protocol/mock'),
    websocket: 'ws://localhost:3032'
    // mock: 'testserver2'
  }
})

// var client2 = global.client2 = new Hub({
//   adapter: {
//     id: 'testclient2',
//     inject: require('../../lib/protocol/mock'),
//     // val: 'ws://localhost:3031'
//     mock: 'testserver2'
//     // scope: 'murder'
//   }
// })

// client2.adapter.scope.val = 'murder'

// client.get('time', {})
// client2.get('time', {})

// client.adapter.mock.connected.is(true, function () {
  // console.log('lullllzzzzzz'.rainbow)
  // setTimeout(() => {
  //   // console.clear()
  //   server.set({
  //     time: 222
  //   })
  // }, 100)
// })

// client.subscribe({
//   time: true
// }, function (data, event) {
//   for (var i in data) {
//     // here we want the real data!
//     console.log('hello', data[i].origin.path.join('.'), data[i].data, event)
//     // console.log(data[i].origin._on.data.getBound(event, this).data)
//   }
// })
//
// client.time.on(function (data) {
//   console.log('mystery ballz', data)
// })
//
// client2.time.on(function (data) {
//   console.log('mystery ballz -- 2', data)
//   // client2.set({
//   //   time: 88888888
//   // })
// })

console.line = false

// setTimeout(function () {
  // console.clear()
  // client2.subscribe({
  //   time: true
  //   // flurps: true
  // }, function (data, event) {
  //   for (var i in data) {
  //     // here we want the real data!
  //     console.log('hello', data[i].origin.path.join('.'), data[i].data, event)
  //     // console.log(data[i].origin._on.data.getBound(event, this).data)
  //   }
  // })

  // console.log('ok expect 222 myster ballz 2 to fire')
// }, 2000)

// client.set({flurps: true})

var o = new Observable({
  x: true
})

o.x.subscribe({
  $upward: {
    time: true
  }
}, function (data, event) {
  console.log('unicorns'.rainbow, event, data)
})

setTimeout(() => {
  console.clear()
  // client.subscribe({
  //   time: true
  // }, function () {
  //   console.log('unicorns'.rainbow)
  // })
  o.val = client
}, 500)

var Element = require('vigour-element')
var App = require('vigour-element/lib/app')

Element.prototype.inject(
  require('vigour-js/lib/operator/subscribe'),
  require('vigour-element/lib/property/text'),
  require('vigour-element/lib/property/css'),
  require('vigour-element/lib/property/style')
)

var Property = require('vigour-element/lib/property')
Property.prototype.inject(
  require('vigour-js/lib/operator/subscribe')
)

setTimeout(function () {
  var app = new App({
    node: document.body,
    style: {
      border: '10px solid red'
    },
    james: {
      node: 'input',
      text: { $: 'time' },
      on: {
        keyup () {
          this.text.origin.val = this.node.value
        }
      }
    }
  })

  app.val = client
// app.youzi.val = client2
}, 500)
<|endoftext|>


<commit_before>class Empresa {
	constructor(nome) {
		this.capital = 0.0;
		this.funcionarios = new ListaTrabalhadores();
		this.projects = [];
		this.activeProjects = [];
		this.nome = nome;
		this.parcelas = [];
		this.balanco = [];
		this.oldProjects = [];
	}

  subtrair_despesas() {
	  this.balanco.push(this.capital);
	  var salarios = 0;
	  for (var i = 0; i < this.funcionarios.len; ++i) {
		  var f = this.funcionarios.get(i);
		  salarios += f.salario;
		  this.capital -= f.salario;
	  }
	  this.balanco.push(-salarios);
	  var p = this.parcelas.pop();
	  if (p) {
		  this.capital -= p;
		  this.balanco.push(-p);
	  } else {
		  this.balanco.push(-0.0);
	  }
	  this.balanco.push(this.capital);
	  if(capital < 0){
		  adicionar_emprestimo(-capital, 0.4);
	  }
  }
  adicionar_emprestimo(val, juros) {
	  this.capital += val;
	  var p = val*(1 + juros/100.0)/12.0;
	  for (var i = 0; i < 12; ++i)
		  if (this.parcelas[i])
			  this.parcelas[i] += p
		  else
			  this.parcelas.push(p);
	  //console.log(this.parcelas);
	  //console.log(this.parcelas[12]);
  }

  addProject(p) {
      this.projects.push(p);
  }

  computeDay(){
	for(let p of this.projects) {
        var total = 0;
        var employeesInProject = this.funcionarios.getByProject(p);
        for(let e of employeesInProject){
            for(let s of e.skills){
                total += s.skill;
            }
        }
        p.progress += Math.ceil((total / p.difficulty));
        if(p.progress >= 100){
            for(let e of employeesInProject){
                e.project = null;
			}
			this.capital += p.revenue;
			this.push(p.revenue);
			this.oldProjects.push(p);
        }
    }
    this.projects = this.projects.filter(function (p) {
        return p.progress < 100;
    });
  }

  getHtmlForProjectsStatus() {
	  var htmlList = "";
      for(let p of this.projects) {
		  var funcsString = "";
		  for(let e of this.funcionarios.getByProject(p)){
			funcsString += " | " + e.pnome + " " + e.unome;
		  }
          htmlList += '

' + p.name + '

' + p.description + ' ' + funcsString + '
\

\

\
                  ' + p.progress + '%\

\

';
      }
      return htmlList;
  }
}
<commit_msg>Empresa.subtrair_despesa fix.<commit_after>class Empresa {
	constructor(nome) {
		this.capital = 0.0;
		this.funcionarios = new ListaTrabalhadores();
		this.projects = [];
		this.activeProjects = [];
		this.nome = nome;
		this.parcelas = [];
		this.balanco = [];
		this.oldProjects = [];
	}

  subtrair_despesas() {
	  this.balanco.push(this.capital);
	  var salarios = 0;
	  for (var i = 0; i < this.funcionarios.len; ++i) {
		  var f = this.funcionarios.get(i);
		  salarios += f.salario;
		  this.capital -= f.salario;
	  }
	  this.balanco.push(-salarios);
	  var p = this.parcelas.pop();
	  if (p) {
		  this.capital -= p;
		  this.balanco.push(-p);
	  } else {
		  this.balanco.push(-0.0);
	  }
	  this.balanco.push(this.capital);
	  if(this.capital < 0){
		  adicionar_emprestimo(-this.capital, 0.4);
	  }
  }
  adicionar_emprestimo(val, juros) {
	  this.capital += val;
	  var p = val*(1 + juros/100.0)/12.0;
	  for (var i = 0; i < 12; ++i)
		  if (this.parcelas[i])
			  this.parcelas[i] += p
		  else
			  this.parcelas.push(p);
	  //console.log(this.parcelas);
	  //console.log(this.parcelas[12]);
  }

  addProject(p) {
      this.projects.push(p);
  }

  computeDay(){
	for(let p of this.projects) {
        var total = 0;
        var employeesInProject = this.funcionarios.getByProject(p);
        for(let e of employeesInProject){
            for(let s of e.skills){
                total += s.skill;
            }
        }
        p.progress += Math.ceil((total / p.difficulty));
        if(p.progress >= 100){
            for(let e of employeesInProject){
                e.project = null;
			}
			this.capital += p.revenue;
			this.push(p.revenue);
			this.oldProjects.push(p);
        }
    }
    this.projects = this.projects.filter(function (p) {
        return p.progress < 100;
    });
  }

  getHtmlForProjectsStatus() {
	  var htmlList = "";
      for(let p of this.projects) {
		  var funcsString = "";
		  for(let e of this.funcionarios.getByProject(p)){
			funcsString += " | " + e.pnome + " " + e.unome;
		  }
          htmlList += '

' + p.name + '

' + p.description + ' ' + funcsString + '
\

\

\
                  ' + p.progress + '%\

\

';
      }
      return htmlList;
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
var far = require('far').create();

far.add(__dirname);

var specificTest = process.argv[2]
if (undefined !== specificTest) {
  far.include(new RegExp('test-' + specificTest + '\.js$'));
} else {
  far.include(/test-.*\.js$/);
}

var verbosity = process.argv[3]
if (undefined !== verbosity) {
  verbosity = Number(verbosity)

  if (1 !== verbosity && 2 !== verbosity) {
    console.log('Please provide a verbosity value of 1 or 2')
    process.exit(1)
  }

  far.verbose(verbosity)
}

far.execute();
<commit_msg>Can now run all tests and pass a custom verbosity<commit_after>#!/usr/bin/env node
var far = require('far').create();

far.add(__dirname);

var specificTest = process.argv[2]
if (undefined !== specificTest && 'all' !== specificTest) {
  far.include(new RegExp('test-' + specificTest + '\.js$'));
} else {
  far.include(/test-.*\.js$/);
}

var verbosity = process.argv[3]
if (undefined !== verbosity) {
  verbosity = Number(verbosity)

  if (1 !== verbosity && 2 !== verbosity) {
    console.log('Please provide a verbosity value of 1 or 2')
    process.exit(1)
  }

  far.verbose(verbosity)
}

far.execute();
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return (

            Welcome to React
            {process.env.NODE_ENV}

          To get started, edit src/App.js and save to reload.

    )
  }
}

export default App

asdfef

afeewf
<commit_msg>fixes lint test<commit_after>import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return (

            Welcome to React
            {process.env.NODE_ENV}

          To get started, edit src/App.js and save to reload.

    )
  }
}

export default App
<|endoftext|>