<commit_before>
describe('instance api',function(){
   "use strict";

   var oboeBus, oboeInstance;

   beforeEach(function(){
      oboeBus = spiedPubSub();

      oboeInstance = instanceApi(oboeBus);
   });

   function anAscent(){
      return list(namedNode(ROOT_PATH, {}) );
   }

   describe('header method', function(){

      it('returns undefined if not available', function() {

         expect( oboeInstance.header() ).toBeUndefined();
      });

      it('can provide object once available', function() {

         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header() ).toEqual(headers);
      });

      it('can provide single header once available', function() {
         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header('x-remainingRequests') ).toEqual(100);
      });

      it('gives undefined for non-existent single header', function() {
         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header('x-remainingBathtubs') ).toBeUndefined();
      });
   });

   describe('root method', function(){

      it('returns undefined if not available', function() {

         expect( oboeInstance.root() ).toBeUndefined();
      });

      it('can provide object once available', function() {

         var root = {I:'am', the:'root'};

         oboeBus(ROOT_PATH_FOUND).emit( root);

         expect( oboeInstance.root() ).toEqual(root);
      });
   });

   describe('node and path callbacks', function(){

      it('calls node callback when notified of matching node', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('node', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('calls path callback when notified of matching path', function() {

         var callback = jasmine.createSpy('path callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('path', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('allows short-cut node matching', function() {

         var callback1 = jasmine.createSpy(),
             callback2 = jasmine.createSpy();

         oboeInstance.on('node', {
            pattern1: callback1,
            pattern2: callback2
         });

         expect(callback1).not.toHaveBeenCalled()
         expect(callback2).not.toHaveBeenCalled()

         oboeBus('node:pattern1').emit( {}, anAscent())

         expect(callback1).toHaveBeenCalled()
         expect(callback2).not.toHaveBeenCalled()

         oboeBus('node:pattern2').emit( {}, anAscent())

         expect(callback2).toHaveBeenCalled()
      });

      it('calls node callback added using 2-arg mode when notified of match to pattern', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('node:a_pattern', callback)

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('allows adding using addListener method', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.addListener('node:a_pattern', callback)

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('calls path callback added using 2-arg mode when notified of match to pattern', function() {

         var callback = jasmine.createSpy('path callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('path:a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('doesn\'t call node callback on path found', function() {

         var callback = jasmine.createSpy('node callback');

         oboeInstance.on('node', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( {}, list(namedNode(ROOT_PATH, {}) ) );

         expect(callback).not.toHaveBeenCalled();
      });

      it('doesn\'t call again after forget called from inside callback', function() {

         var callback = jasmine.createSpy().andCallFake(function(){
                           this.forget();
                        }),
             ascent =   list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', callback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback.call.length).toBe(1)

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback.calls.length).toBe(1)
      });

      it('doesn\'t call node callback after callback is removed', function() {

         var callback = jasmine.createSpy(),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', callback);
         oboeInstance.removeListener('node', 'a_pattern', callback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback).not.toHaveBeenCalled()
      });

      it('doesn\'t call node callback after callback is removed using 2-arg form', function() {

         var callback = jasmine.createSpy(),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', callback);
         oboeInstance.removeListener('node:a_pattern', callback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback).not.toHaveBeenCalled()
      });

      it('doesn\'t call path callback after callback is removed', function() {

         var callback = jasmine.createSpy(),
             ascent = list(namedNode('path', {}));

         oboeInstance.on('path', 'a_pattern', callback);
         oboeInstance.removeListener('path', 'a_pattern', callback);

         oboeBus('path:a_pattern').emit( {}, ascent);

         expect(callback).not.toHaveBeenCalled()
      });

      it('doesn\'t remove callback if wrong pattern is removed', function() {

         var callback = jasmine.createSpy(),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', callback);

         oboeInstance.removeListener('node', 'wrong_pattern', callback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback).toHaveBeenCalled()
      });

      it('doesn\'t remove callback if wrong callback is removed', function() {

         var callback = jasmine.createSpy(),
             wrongCallback = jasmine.createSpy(),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', callback);

         oboeInstance.removeListener('node', 'a_pattern', wrongCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(callback).toHaveBeenCalled()
      });

      it('allows nodes node to be removed in a different ' +
         'style than they were added', function() {

         var
             callback1 = jasmine.createSpy(),
             callback2 = jasmine.createSpy(),
             callback3 = jasmine.createSpy(),
             ascent = list(namedNode('node', {}));

         oboeInstance.node('pattern1', callback1);
         oboeInstance.on('node', 'pattern2', callback2);
         oboeInstance.on('node', {pattern3: callback3});

         oboeInstance.removeListener('node:pattern1', callback1);
         oboeInstance.removeListener('node:pattern2', callback2);
         oboeInstance.removeListener('node:pattern3', callback3);

         oboeBus('node:pattern1').emit( {}, ascent);
         oboeBus('node:pattern2').emit( {}, ascent);
         oboeBus('node:pattern3').emit( {}, ascent);

         expect(callback1).not.toHaveBeenCalled()
         expect(callback2).not.toHaveBeenCalled()
         expect(callback3).not.toHaveBeenCalled()
      });
   });

   describe('start event', function() {
      it('notifies .on(start) listener when http response starts', function(){
         var callback = jasmine.createSpy();

         oboeInstance.on('start', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(callback).toHaveBeenCalledWith( 200, {a_header:'foo'} )
      });

      it('notifies .start listener when http response starts', function(){
         var callback = jasmine.createSpy();

         oboeInstance.start(callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(callback).toHaveBeenCalledWith( 200, {a_header:'foo'} )
      });

      it('can be de-registered', function() {
         var callback = jasmine.createSpy();

         oboeInstance.on('start', callback);
         oboeInstance.removeListener('start', callback);

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(callback).not.toHaveBeenCalled()
      });
   });

   describe('done event', function(){

      it('calls listener on end of JSON when added using .on(done)', function() {
         var callback = jasmine.createSpy();

         oboeInstance.on('done', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:!').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
      });

      it('calls listener on end of JSON when added using .done', function() {
         var callback = jasmine.createSpy();

         oboeInstance.done(callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:!').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
      });

      it('can be de-registered', function() {
         var callback = jasmine.createSpy();

         oboeInstance.on('done', callback);
         oboeInstance.removeListener('done', callback);

         oboeBus('node:!').emit( {}, anAscent())

         expect(callback).not.toHaveBeenCalled()
      });
   });

   it('emits ABORTING when .abort() is called', function() {
      oboeInstance.abort();
      expect(oboeBus(ABORTING).emit).toHaveBeenCalled()
   });

   describe('errors cases', function(){

      describe('calling fail listener', function() {

         it('notifies .on(fail) listener when something fails', function(){
            var callback = jasmine.createSpy();

            oboeInstance.on('fail', callback);

            expect(callback).not.toHaveBeenCalled()

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(callback).toHaveBeenCalledWith( 'something went wrong' )
         });

         it('notifies .fail listener when something fails', function(){
            var callback = jasmine.createSpy();

            oboeInstance.fail(callback);

            expect(callback).not.toHaveBeenCalled()

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(callback).toHaveBeenCalledWith( 'something went wrong' )
         });

         it('can be de-registered', function() {
            var callback = jasmine.createSpy();

            oboeInstance.on('fail', callback);
            oboeInstance.removeListener('fail', callback);

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(callback).not.toHaveBeenCalled()
         });
      });

      it('is protected from error in node callback', function() {
         var e = "an error";
         var callback = jasmine.createSpy().andThrow(e);

         expect(function(){
            oboeInstance.on('node', 'a_pattern', callback);
         }).not.toThrow();

         oboeBus('node:a_pattern').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in node callback added via shortcut', function() {
         var e = "an error";
         var callback = jasmine.createSpy().andThrow(e);

         expect(function(){
            oboeInstance.on('node', {'a_pattern': callback});
         }).not.toThrow();

         oboeBus('node:a_pattern').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in path callback', function() {
         var e = "an error";
         var callback = jasmine.createSpy().andThrow(e);

         expect(function(){
            oboeInstance.on('path', 'a_pattern', callback);
         }).not.toThrow();

         oboeBus('path:a_pattern').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in start callback', function() {
         var e = "an error";
         var callback = jasmine.createSpy().andThrow(e);

         expect(function(){
            oboeInstance.on('start', callback);
         }).not.toThrow();

         oboeBus(HTTP_START).emit()

         expect(callback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in done callback', function() {
         var e = "an error";
         var callback = jasmine.createSpy().andThrow(e);

         expect(function(){
            oboeInstance.done( callback);
         }).not.toThrow();

         oboeBus( 'node:!').emit( {}, anAscent())

         expect(callback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

   });

   describe('unknown event types', function() {

      it('can be added and fired', function() {
         var spy1 = jasmine.createSpy();
         var spy2 = jasmine.createSpy();

         expect(function(){
            oboeInstance
               .on('xyzzy', spy1)
               .on('end_of_universe', spy2);
         }).not.toThrow();

         oboeInstance.emit('xyzzy', 'hello');
         oboeInstance.emit('end_of_universe', 'oh no!');

         expect( spy1 ).toHaveBeenCalledWith('hello');
         expect( spy2 ).toHaveBeenCalledWith('oh no!');
      });

      it('is allows removal', function() {
         var spy1 = jasmine.createSpy();
         var spy2 = jasmine.createSpy();

         oboeInstance
            .on('xyzzy', spy1)
            .on('end_of_universe', spy2);

         oboeInstance.removeListener('xyzzy', spy1);
         oboeInstance.removeListener('end_of_universe', spy2);

         oboeInstance.emit('xyzzy', 'hello');
         oboeInstance.emit('end_of_universe', 'oh no!');

         expect( spy1 ).not.toHaveBeenCalled()
         expect( spy2 ).not.toHaveBeenCalled()
      });
   });

});
<commit_msg>give some callbacks names<commit_after>
describe('instance api',function(){
   "use strict";

   var oboeBus, oboeInstance;

   beforeEach(function(){
      oboeBus = spiedPubSub();

      oboeInstance = instanceApi(oboeBus);
   });

   function anAscent(){
      return list(namedNode(ROOT_PATH, {}) );
   }

   describe('header method', function(){

      it('returns undefined if not available', function() {

         expect( oboeInstance.header() ).toBeUndefined();
      });

      it('can provide object once available', function() {

         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header() ).toEqual(headers);
      });

      it('can provide single header once available', function() {
         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header('x-remainingRequests') ).toEqual(100);
      });

      it('gives undefined for non-existent single header', function() {
         var headers = {"x-remainingRequests": 100};

         oboeBus(HTTP_START).emit( 200, headers );

         expect( oboeInstance.header('x-remainingBathtubs') ).toBeUndefined();
      });
   });

   describe('root method', function(){

      it('returns undefined if not available', function() {

         expect( oboeInstance.root() ).toBeUndefined();
      });

      it('can provide object once available', function() {

         var root = {I:'am', the:'root'};

         oboeBus(ROOT_PATH_FOUND).emit( root);

         expect( oboeInstance.root() ).toEqual(root);
      });
   });

   describe('node and path callbacks', function(){

      it('calls node callback when notified of matching node', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('node', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('calls path callback when notified of matching path', function() {

         var callback = jasmine.createSpy('path callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('path', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('allows short-cut node matching', function() {

         var pattern1Callback = jasmine.createSpy('pattern 1 callback'),
             pattern2Callback = jasmine.createSpy('pattern 2 callback');

         oboeInstance.on('node', {
            pattern1: pattern1Callback,
            pattern2: pattern2Callback
         });

         expect(pattern1Callback).not.toHaveBeenCalled()
         expect(pattern2Callback).not.toHaveBeenCalled()

         oboeBus('node:pattern1').emit( {}, anAscent())

         expect(pattern1Callback).toHaveBeenCalled()
         expect(pattern2Callback).not.toHaveBeenCalled()

         oboeBus('node:pattern2').emit( {}, anAscent())

         expect(pattern2Callback).toHaveBeenCalled()
      });

      it('calls node callback added using 2-arg mode when notified of match to pattern', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('node:a_pattern', callback)

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('allows adding using addListener method', function() {

         var callback = jasmine.createSpy('node callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.addListener('node:a_pattern', callback)

         expect(callback).not.toHaveBeenCalled()

         oboeBus('node:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('calls path callback added using 2-arg mode when notified of match to pattern', function() {

         var callback = jasmine.createSpy('path callback'),
             node = {},
             path = [],
             ancestors = [];

         oboeInstance.on('path:a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( node, path, ancestors );

         expect(callback).toHaveBeenCalledWith( node, path, ancestors );
      });

      it('doesn\'t call node callback on path found', function() {

         var callback = jasmine.createSpy('node callback');

         oboeInstance.on('node', 'a_pattern', callback);

         expect(callback).not.toHaveBeenCalled()

         oboeBus('path:a_pattern').emit( {}, list(namedNode(ROOT_PATH, {}) ) );

         expect(callback).not.toHaveBeenCalled();
      });

      it('doesn\'t call again after forget called from inside callback', function() {

         var nodeCallback = jasmine.createSpy('node callback').andCallFake(function(){
                           this.forget();
                        }),
             ascent =   list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', nodeCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(nodeCallback.call.length).toBe(1)

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(nodeCallback.calls.length).toBe(1)
      });

      it('doesn\'t call node callback after callback is removed', function() {

         var nodeCallback = jasmine.createSpy('node callback'),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', nodeCallback);
         oboeInstance.removeListener('node', 'a_pattern', nodeCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(nodeCallback).not.toHaveBeenCalled()
      });

      it('doesn\'t call node callback after callback is removed using 2-arg form', function() {

         var nodeCallback = jasmine.createSpy('node callback'),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', nodeCallback);
         oboeInstance.removeListener('node:a_pattern', nodeCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(nodeCallback).not.toHaveBeenCalled()
      });

      it('doesn\'t call path callback after callback is removed', function() {

         var pathCallback = jasmine.createSpy('path callback'),
             ascent = list(namedNode('path', {}));

         oboeInstance.on('path', 'a_pattern', pathCallback);
         oboeInstance.removeListener('path', 'a_pattern', pathCallback);

         oboeBus('path:a_pattern').emit( {}, ascent);

         expect(pathCallback).not.toHaveBeenCalled()
      });

      it('doesn\'t remove callback if wrong pattern is removed', function() {

         var nodeCallback = jasmine.createSpy('node callback'),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', nodeCallback);

         oboeInstance.removeListener('node', 'wrong_pattern', nodeCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(nodeCallback).toHaveBeenCalled()
      });

      it('doesn\'t remove callback if wrong callback is removed', function() {

         var correctCallback = jasmine.createSpy('correct callback'),
             wrongCallback = jasmine.createSpy('wrong callback'),
             ascent = list(namedNode('node', {}));

         oboeInstance.on('node', 'a_pattern', correctCallback);

         oboeInstance.removeListener('node', 'a_pattern', wrongCallback);

         oboeBus('node:a_pattern').emit( {}, ascent);

         expect(correctCallback).toHaveBeenCalled()
      });

      it('allows nodes node to be removed in a different ' +
         'style than they were added', function() {

         var
             callback1 = jasmine.createSpy('callback 1'),
             callback2 = jasmine.createSpy('callback 2'),
             callback3 = jasmine.createSpy('callback 3'),
             ascent = list(namedNode('node', {}));

         oboeInstance.node('pattern1', callback1);
         oboeInstance.on('node', 'pattern2', callback2);
         oboeInstance.on('node', {pattern3: callback3});

         oboeInstance.removeListener('node:pattern1', callback1);
         oboeInstance.removeListener('node:pattern2', callback2);
         oboeInstance.removeListener('node:pattern3', callback3);

         oboeBus('node:pattern1').emit( {}, ascent);
         oboeBus('node:pattern2').emit( {}, ascent);
         oboeBus('node:pattern3').emit( {}, ascent);

         expect(callback1).not.toHaveBeenCalled()
         expect(callback2).not.toHaveBeenCalled()
         expect(callback3).not.toHaveBeenCalled()
      });
   });

   describe('start event', function() {
      it('notifies .on(start) listener when http response starts', function(){
         var startCallback = jasmine.createSpy('start callback');

         oboeInstance.on('start', startCallback);

         expect(startCallback).not.toHaveBeenCalled()

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(startCallback).toHaveBeenCalledWith( 200, {a_header:'foo'} )
      });

      it('notifies .start listener when http response starts', function(){
         var startCallback = jasmine.createSpy('start callback');

         oboeInstance.start(startCallback);

         expect(startCallback).not.toHaveBeenCalled()

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(startCallback).toHaveBeenCalledWith( 200, {a_header:'foo'} )
      });

      it('can be de-registered', function() {
         var startCallback = jasmine.createSpy('start callback');

         oboeInstance.on('start', startCallback);
         oboeInstance.removeListener('start', startCallback);

         oboeBus(HTTP_START).emit( 200, {a_header:'foo'} )

         expect(startCallback).not.toHaveBeenCalled()
      });
   });

   describe('done event', function(){

      it('calls listener on end of JSON when added using .on(done)', function() {
         var doneCallback = jasmine.createSpy('done callback');

         oboeInstance.on('done', doneCallback);

         expect(doneCallback).not.toHaveBeenCalled()

         oboeBus('node:!').emit( {}, anAscent())

         expect(doneCallback).toHaveBeenCalled()
      });

      it('calls listener on end of JSON when added using .done', function() {
         var doneCallback = jasmine.createSpy('done callback');

         oboeInstance.done(doneCallback);

         expect(doneCallback).not.toHaveBeenCalled()

         oboeBus('node:!').emit( {}, anAscent())

         expect(doneCallback).toHaveBeenCalled()
      });

      it('can be de-registered', function() {
         var doneCallback = jasmine.createSpy('done callback');

         oboeInstance.on('done', doneCallback);
         oboeInstance.removeListener('done', doneCallback);

         oboeBus('node:!').emit( {}, anAscent())

         expect(doneCallback).not.toHaveBeenCalled()
      });
   });

   it('emits ABORTING when .abort() is called', function() {
      oboeInstance.abort();
      expect(oboeBus(ABORTING).emit).toHaveBeenCalled()
   });

   describe('errors cases', function(){

      describe('calling fail listener', function() {

         it('notifies .on(fail) listener when something fails', function(){
            var failCallback = jasmine.createSpy('fail callback');

            oboeInstance.on('fail', failCallback);

            expect(failCallback).not.toHaveBeenCalled()

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(failCallback).toHaveBeenCalledWith( 'something went wrong' )
         });

         it('notifies .fail listener when something fails', function(){
            var failCallback = jasmine.createSpy('fail callback');

            oboeInstance.fail(failCallback);

            expect(failCallback).not.toHaveBeenCalled()

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(failCallback).toHaveBeenCalledWith( 'something went wrong' )
         });

         it('can be de-registered', function() {
            var failCallback = jasmine.createSpy('fail callback');

            oboeInstance.on('fail', failCallback);
            oboeInstance.removeListener('fail', failCallback);

            oboeBus(FAIL_EVENT).emit( 'something went wrong' )

            expect(failCallback).not.toHaveBeenCalled()
         });
      });

      it('is protected from error in node callback', function() {
         var e = "an error";
         var nodeCallback = jasmine.createSpy('nodeCallback').andThrow(e);

         expect(function(){
            oboeInstance.on('node', 'a_pattern', nodeCallback);
         }).not.toThrow();

         oboeBus('node:a_pattern').emit( {}, anAscent())

         expect(nodeCallback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in node callback added via shortcut', function() {
         var e = "an error";
         var nodeCallback = jasmine.createSpy('node callback').andThrow(e);

         expect(function(){
            oboeInstance.on('node', {'a_pattern': nodeCallback});
         }).not.toThrow();

         oboeBus('node:a_pattern').emit( {}, anAscent())

         expect(nodeCallback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in path callback', function() {
         var e = "an error";
         var pathCallback = jasmine.createSpy('path callback').andThrow(e);

         expect(function(){
            oboeInstance.on('path', 'a_pattern', pathCallback);
         }).not.toThrow();

         oboeBus('path:a_pattern').emit( {}, anAscent())

         expect(pathCallback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in start callback', function() {
         var e = "an error";
         var startCallback = jasmine.createSpy('start callback').andThrow(e);

         expect(function(){
            oboeInstance.on('start', startCallback);
         }).not.toThrow();

         oboeBus(HTTP_START).emit()

         expect(startCallback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

      it('is protected from error in done callback', function() {
         var e = "an error";
         var doneCallback = jasmine.createSpy('done callback').andThrow(e);

         expect(function(){
            oboeInstance.done( doneCallback);
         }).not.toThrow();

         oboeBus( 'node:!').emit( {}, anAscent())

         expect(doneCallback).toHaveBeenCalled()
         expect(oboeBus(FAIL_EVENT).emit)
            .toHaveBeenCalledWith(errorReport(undefined, undefined, e))
      });

   });

   describe('unknown event types', function() {

      it('can be added and fired', function() {
         var spy1 = jasmine.createSpy('xyzzy callback');
         var spy2 = jasmine.createSpy('end of universe callback');

         expect(function(){
            oboeInstance
               .on('xyzzy', spy1)
               .on('end_of_universe', spy2);
         }).not.toThrow();

         oboeInstance.emit('xyzzy', 'hello');
         oboeInstance.emit('end_of_universe', 'oh no!');

         expect( spy1 ).toHaveBeenCalledWith('hello');
         expect( spy2 ).toHaveBeenCalledWith('oh no!');
      });

      it('is allows removal', function() {
         var spy1 = jasmine.createSpy('xyzzy callback');
         var spy2 = jasmine.createSpy('end of universe callback');

         oboeInstance
            .on('xyzzy', spy1)
            .on('end_of_universe', spy2);

         oboeInstance.removeListener('xyzzy', spy1);
         oboeInstance.removeListener('end_of_universe', spy2);

         oboeInstance.emit('xyzzy', 'hello');
         oboeInstance.emit('end_of_universe', 'oh no!');

         expect( spy1 ).not.toHaveBeenCalled()
         expect( spy2 ).not.toHaveBeenCalled()
      });
   });

});
<|endoftext|>


<commit_before>//
// パズル固有スクリプト部 因子の部屋版 factors.js v3.4.0
//
pzpr.createCustoms('factors', {
//---------------------------------------------------------
// マウス入力系
MouseEvent:{
	mouseinput : function(){
		if(this.owner.playmode){
			if(this.mousestart){ this.inputqnum();}
		}
		else if(this.owner.editmode){
			if(this.mousestart || this.mousemove){
				if(this.btn.Left){ this.inputborder();}
			}
			else if(this.mouseend && this.notInputted()){
				this.inputqnum();
			}
		}
	},

	inputqnum_main : function(cell){
		var max=cell.nummaxfunc(), min=cell.numminfunc();
		var num=(this.owner.editmode ? cell.getQnum() : cell.getAnum()), val=-1;

		// playmode: subtypeは0以上、 qsにqsub値が入る
		// editmode: subtypeは-1固定、qsは常に0が入る
		if(this.btn.Left){
			if     (num>=max){ val = -1;}
			else if(num===-1){ val = 1;}
			else{ val = num+1;}
		}
		else if(this.btn.Right){
			if     (num===-1){ val = max;}
			else if(num<=min){ val = -1;}
			else{ val = num-1;}
		}
		cell.setNum(val);

		cell.draw();
	}
},

//---------------------------------------------------------
// キーボード入力系
KeyEvent:{
	enablemake : true,
	enableplay : true
},

//---------------------------------------------------------
// 盤面管理系
Cell:{
	disInputHatena : true,

	nummaxfunc : function(){
		return this.owner.editmode?999999:Math.max(this.owner.board.qcols,this.owner.board.qrows);
	},

	setNum : function(val){
		if(val===0){ return;}
		if(this.owner.editmode){ this.setQnum(val);}else{ this.setAnum(val);}
	}
},

Board:{
	qcols : 9,
	qrows : 9,

	hasborder : 1
},

AreaRoomManager:{
	enabled : true,
	hastop : true
},

//---------------------------------------------------------
// 画像表示系
Graphic:{
	initialize : function(){
		this.Common.prototype.initialize.call(this);

		this.gridcolor = this.gridcolor_DLIGHT;
	},
	paint : function(){
		this.drawBGCells();
		this.drawGrid();

		this.drawNumbers_factors();

		this.drawBorders();

		this.drawChassis();

		this.drawCursor();
	},

	drawNumbers_factors : function(){
		var g = this.vinc('cell_number', 'auto');

		var clist = this.range.cells;
		for(var i=0;i<clist.length;i++){
			var cell = clist[i];
			var key_qans = ['cell',cell.id,'qans'].join('_');
			var key_ques = ['cell',cell.id,'ques'].join('_');
			var px = cell.bx*this.bw, py = cell.by*this.bh;

			if(cell.anum!==-1){
				var color = (cell.error==1?this.fontErrcolor:this.fontAnscolor);
				var size = (cell.anum<10?0.8:0.7);
				this.dispnum(key_qans, 1, (""+cell.anum), size, color, px, py);
			}
			else{ this.hidenum(key_qans);}

			if(cell.qnum!==-1){
				var size = 0.45;
				if     (cell.qnum>=100000){ size = 0.30;}
				else if(cell.qnum>= 10000){ size = 0.36;}
				this.dispnum(key_ques, 5, (""+cell.qnum), size, this.fontcolor, px, py);
			}
			else{ this.hidenum(key_ques);}
		}
	}
},

//---------------------------------------------------------
// URLエンコード/デコード処理
Encode:{
	decodePzpr : function(type){
		this.decodeBorder();
		this.decodeRoomNumber16();
	},
	encodePzpr : function(type){
		this.encodeBorder();
		this.encodeRoomNumber16();
	}
},
//---------------------------------------------------------
FileIO:{
	decodeData : function(){
		this.decodeBorderQues();
		this.decodeCellQnum();
		this.decodeCellAnumsub();
	},
	encodeData : function(){
		this.encodeBorderQues();
		this.encodeCellQnum();
		this.encodeCellAnumsub();
	}
},

//---------------------------------------------------------
// 正解判定処理実行部
AnsCheck:{
	checkAns : function(){

		if( !this.checkRowsColsSameAnsNumber() ){ return 'nmDupRow';}

		var rinfo = this.owner.board.getRoomInfo();
		if( !this.checkRoomNumber(rinfo) ){ return 'nmProduct';}

		if( !this.checkNoNumCell() ){ return 'ceEmpty';}

		return null;
	},
	check1st : function(){
		return (this.checkNoNumCell() ? null : 'ceEmpty');
	},

	checkRowsColsSameAnsNumber : function(){
		return this.checkRowsCols(this.isDifferentNumberInClist, function(cell){ return cell.getAnum();});
	},

	checkRoomNumber : function(rinfo){
		var result = true;
		for(var id=1;id<=rinfo.max;id++){
			var product = 1, clist = rinfo.room[id].clist;
			for(var i=0;i<clist.length;i++){
				var cell = clist[i];
				if(cell.getAnum()>0){ product *= cell.getAnum();}
				else{ product = 0;}
			}
			if(product==0){ continue;}

			var cell = this.owner.board.rooms.getTopOfRoom(id);
			if(product!=cell.getQnum()){
				if(this.checkOnly){ return false;}
				clist.seterr(1);
				result = false;
			}
		}
		return result;
	}
},

FailCode:{
	nmProduct : ["ブロックの数字と数字の積が同じではありません。","A number of room is not equal to the product of these numbers."],
	ceEmpty : ["数字の入っていないマスがあります。","There is an empty cell."]
}
});
<commit_msg>factors: 正答判定で、部屋の数字取得不具合と数字の入っているセルの誤検出を修正<commit_after>//
// パズル固有スクリプト部 因子の部屋版 factors.js v3.4.0
//
pzpr.createCustoms('factors', {
//---------------------------------------------------------
// マウス入力系
MouseEvent:{
	mouseinput : function(){
		if(this.owner.playmode){
			if(this.mousestart){ this.inputqnum();}
		}
		else if(this.owner.editmode){
			if(this.mousestart || this.mousemove){
				if(this.btn.Left){ this.inputborder();}
			}
			else if(this.mouseend && this.notInputted()){
				this.inputqnum();
			}
		}
	},

	inputqnum_main : function(cell){
		var max=cell.nummaxfunc(), min=cell.numminfunc();
		var num=(this.owner.editmode ? cell.getQnum() : cell.getAnum()), val=-1;

		// playmode: subtypeは0以上、 qsにqsub値が入る
		// editmode: subtypeは-1固定、qsは常に0が入る
		if(this.btn.Left){
			if     (num>=max){ val = -1;}
			else if(num===-1){ val = 1;}
			else{ val = num+1;}
		}
		else if(this.btn.Right){
			if     (num===-1){ val = max;}
			else if(num<=min){ val = -1;}
			else{ val = num-1;}
		}
		cell.setNum(val);

		cell.draw();
	}
},

//---------------------------------------------------------
// キーボード入力系
KeyEvent:{
	enablemake : true,
	enableplay : true
},

//---------------------------------------------------------
// 盤面管理系
Cell:{
	disInputHatena : true,

	nummaxfunc : function(){
		return this.owner.editmode?999999:Math.max(this.owner.board.qcols,this.owner.board.qrows);
	},

	setNum : function(val){
		if(val===0){ return;}
		if(this.owner.editmode){ this.setQnum(val);}else{ this.setAnum(val);}
	}
},

CellList:{
	getProduct : function(){
		var product = 1;
		for(var i=0,len=this.length;i<len;i++){
			var num = this[i].getAnum();
			product *= (num>0 ? num : 0);
		}
		return product;
	}
},

Board:{
	qcols : 9,
	qrows : 9,

	hasborder : 1
},

AreaRoomManager:{
	enabled : true,
	hastop : true
},

//---------------------------------------------------------
// 画像表示系
Graphic:{
	initialize : function(){
		this.Common.prototype.initialize.call(this);

		this.gridcolor = this.gridcolor_DLIGHT;
	},
	paint : function(){
		this.drawBGCells();
		this.drawGrid();

		this.drawNumbers_factors();

		this.drawBorders();

		this.drawChassis();

		this.drawCursor();
	},

	drawNumbers_factors : function(){
		var g = this.vinc('cell_number', 'auto');

		var clist = this.range.cells;
		for(var i=0;i<clist.length;i++){
			var cell = clist[i];
			var key_qans = ['cell',cell.id,'qans'].join('_');
			var key_ques = ['cell',cell.id,'ques'].join('_');
			var px = cell.bx*this.bw, py = cell.by*this.bh;

			if(cell.anum!==-1){
				var color = (cell.error==1?this.fontErrcolor:this.fontAnscolor);
				var size = (cell.anum<10?0.8:0.7);
				this.dispnum(key_qans, 1, (""+cell.anum), size, color, px, py);
			}
			else{ this.hidenum(key_qans);}

			if(cell.qnum!==-1){
				var size = 0.45;
				if     (cell.qnum>=100000){ size = 0.30;}
				else if(cell.qnum>= 10000){ size = 0.36;}
				this.dispnum(key_ques, 5, (""+cell.qnum), size, this.fontcolor, px, py);
			}
			else{ this.hidenum(key_ques);}
		}
	}
},

//---------------------------------------------------------
// URLエンコード/デコード処理
Encode:{
	decodePzpr : function(type){
		this.decodeBorder();
		this.decodeRoomNumber16();
	},
	encodePzpr : function(type){
		this.encodeBorder();
		this.encodeRoomNumber16();
	}
},
//---------------------------------------------------------
FileIO:{
	decodeData : function(){
		this.decodeBorderQues();
		this.decodeCellQnum();
		this.decodeCellAnumsub();
	},
	encodeData : function(){
		this.encodeBorderQues();
		this.encodeCellQnum();
		this.encodeCellAnumsub();
	}
},

//---------------------------------------------------------
// 正解判定処理実行部
AnsCheck:{
	checkAns : function(){

		if( !this.checkRowsColsSameAnsNumber() ){ return 'nmDupRow';}

		var rinfo = this.owner.board.getRoomInfo();
		if( !this.checkRoomNumber(rinfo) ){ return 'nmProduct';}

		if( !this.checkNoAnumCell() ){ return 'ceEmpty';}

		return null;
	},
	check1st : function(){
		return (this.checkNoAnumCell() ? null : 'ceEmpty');
	},

	checkRowsColsSameAnsNumber : function(){
		return this.checkRowsCols(this.isDifferentNumberInClist, function(cell){ return cell.getAnum();});
	},
	checkNoAnumCell : function(){
		return this.checkAllCell( function(cell){ return cell.getAnum()===-1;} );
	},

	checkRoomNumber : function(rinfo){
		var result = true;
		for(var id=1;id<=rinfo.max;id++){
			var room = rinfo.room[id], clist = room.clist;
			var product = clist.getProduct();
			if(product === 0){ continue;}

			if(product !== room.top.getQnum()){
				if(this.checkOnly){ return false;}
				clist.seterr(1);
				result = false;
			}
		}
		return result;
	}
},

FailCode:{
	nmProduct : ["ブロックの数字と数字の積が同じではありません。","A number of room is not equal to the product of these numbers."],
	ceEmpty : ["数字の入っていないマスがあります。","There is an empty cell."]
}
});
<|endoftext|>


<commit_before>var _seperator = '_';

var _handlerPartnerMenu = function (event) {
  event.preventDefault();
  appendToHashTag(event.data);
};

function createLinkItem (item) {
  var _a = $("<a/>", { href: "#" + item.link });
  $("<i/>", { class: item.icon }).appendTo(_a);
  $("<span/>", { text: " " + item.name }).appendTo(_a);
  return _a;
}

function createSingleLevelMenuItem (item) {
  return $("<li/>").append(createLinkItem(item));
}

function createMultiLevelMenuItem (item) {
  item.link = "";
  var _a = createLinkItem(item);
  $("<span/>", { class: "label label-primary pull-right", text: item.sub.length - 1 }).appendTo(_a);

  var _ul = $("<ul/>", { class: "treeview-menu" });
  $.each(item.sub, function (index, item) {
    item.icon = index == 0 ? "fa fa-certificate" : "fa fa-circle-o";
    _ul.append(createSingleLevelMenuItem(item));
  });

  return $("<li/>", { class: "treeview" }).append(_a).append(_ul);
}

function renderEnvironmentMenuItems () {
  var menu_env = $("#menu-env");
  var item_list = []
  $.each(menu_env_data, function (index, item) {
    item_list.push(item.sub
      ? createMultiLevelMenuItem(item)
      : createSingleLevelMenuItem(item));
  });

  $.each(item_list.reverse(), function () {
    menu_env.after(this);
  });
}

function renderPartnerMenuItems () {
  var menu_part = $("#menu-part");
  var item_list = []
  $.each(menu_part_data, function (index, item) {
    item_list.push(createSingleLevelMenuItem(item).click(item.link, _handlerPartnerMenu));
  });

  $.each(item_list.reverse(), function () {
    menu_part.after(this);
  });
}

function hasHasTagInUrl () {
  return window.location.hash.length > 0;
}

function extractHashTags () {
  var tags = window.location.hash.substr(1).split(_seperator);
  return {
    "env": tags[0],
    "part": tags[1],
  };
}

function appendToHashTag (suffix) {
  var tag = extractHashTags();
  var result = false;
  if (tag.env) {
    window.location.hash = tag.part == suffix ? tag.env : tag.env + _seperator + suffix;
    result = true;
  }
  return result;
}

function activateTaggedMenuItems () {
  $(".sidebar-menu li[class~=active]").removeClass("active");
  var tags = extractHashTags();
  if (tags.env || tags.part) {
    $.each(tags, function (index, tag) {
      if (tag) {
        var selector = ".sidebar-menu a[href=#" + tag + "]";
        var target = $(selector);
        if (target) {
          target.parent("li").addClass("active");
          target.parent("li").parent().parent("li").addClass("active");
        }
      }
    });
  }
  if (tags.env) {
    // Collapse other expanded menu items
    var selector = "a[href=#" + tags.env + "]";
    $(".sidebar-menu ul.menu-open").each(function (index) {
      if ($(this).find(selector).length == 0) {
        $(this).siblings().trigger("click");
      };
    });
  }
  else {
    // For default page, just collapse every item
    $(".sidebar-menu ul.menu-open").siblings().trigger("click");
  }
}
<commit_msg>Fix un-controlled menu item if it's half-expanded before got collapsed<commit_after>var _seperator = '_';

var _handlerPartnerMenu = function (event) {
  event.preventDefault();
  appendToHashTag(event.data);
};

function createLinkItem (item) {
  var _a = $("<a/>", { href: "#" + item.link });
  $("<i/>", { class: item.icon }).appendTo(_a);
  $("<span/>", { text: " " + item.name }).appendTo(_a);
  return _a;
}

function createSingleLevelMenuItem (item) {
  return $("<li/>").append(createLinkItem(item));
}

function createMultiLevelMenuItem (item) {
  item.link = "";
  var _a = createLinkItem(item);
  $("<span/>", { class: "label label-primary pull-right", text: item.sub.length - 1 }).appendTo(_a);

  var _ul = $("<ul/>", { class: "treeview-menu" });
  $.each(item.sub, function (index, item) {
    item.icon = index == 0 ? "fa fa-certificate" : "fa fa-circle-o";
    _ul.append(createSingleLevelMenuItem(item));
  });

  return $("<li/>", { class: "treeview" }).append(_a).append(_ul);
}

function renderEnvironmentMenuItems () {
  var menu_env = $("#menu-env");
  var item_list = []
  $.each(menu_env_data, function (index, item) {
    item_list.push(item.sub
      ? createMultiLevelMenuItem(item)
      : createSingleLevelMenuItem(item));
  });

  $.each(item_list.reverse(), function () {
    menu_env.after(this);
  });
}

function renderPartnerMenuItems () {
  var menu_part = $("#menu-part");
  var item_list = []
  $.each(menu_part_data, function (index, item) {
    item_list.push(createSingleLevelMenuItem(item).click(item.link, _handlerPartnerMenu));
  });

  $.each(item_list.reverse(), function () {
    menu_part.after(this);
  });
}

function hasHasTagInUrl () {
  return window.location.hash.length > 0;
}

function extractHashTags () {
  var tags = window.location.hash.substr(1).split(_seperator);
  return {
    "env": tags[0],
    "part": tags[1],
  };
}

function appendToHashTag (suffix) {
  var tag = extractHashTags();
  var result = false;
  if (tag.env) {
    window.location.hash = tag.part == suffix ? tag.env : tag.env + _seperator + suffix;
    result = true;
  }
  return result;
}

function activateTaggedMenuItems () {
  $(".sidebar-menu li[class~=active]").removeClass("active");
  var tags = extractHashTags();
  if (tags.env || tags.part) {
    $.each(tags, function (index, tag) {
      if (tag) {
        var selector = ".sidebar-menu a[href=#" + tag + "]";
        var target = $(selector);
        if (target) {
          target.parent("li").addClass("active");
          target.parent("li").parent().parent("li").addClass("active");
        }
      }
    });
  }
  // Only when it's expanded
  if (!$(".sidebar-collapse").length) {
    if (tags.env) {
      // Collapse other expanded menu items
      var selector = "a[href=#" + tags.env + "]";
      $(".sidebar-menu ul.menu-open").each(function (index) {
        if ($(this).find(selector).length == 0) {
          $(this).siblings().trigger("click");
        };
      });
    }
    else {
      // For default page, just collapse every item
      $(".sidebar-menu ul.menu-open").siblings().trigger("click");
    }
  }
}
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2021
 */

import moment from 'moment';
import { PermissionActions } from '../PermissionActions';
import BleService from '../../bluetooth/BleService';
import TemperatureLogManager from '../../bluetooth/TemperatureLogManager';
import SensorManager from '../../bluetooth/SensorManager';
import { UIDatabase } from '../../database';
import { isValidMacAddress, VACCINE_CONSTANTS } from '../../utilities/modules/vaccines/index';
import {
  DOWNLOADING_ERROR_CODES,
  VACCINE_ENTITIES,
} from '../../utilities/modules/vaccines/constants';
import { syncStrings } from '../../localization';
import { selectIsSyncingTemps } from '../../selectors/Bluetooth/sensorDownload';
import { BreachActions } from '../BreachActions';

export const DOWNLOAD_ACTIONS = {
  DOWNLOAD_LOGS_START: 'Bluetooth/downloadLogsStart',
  DOWNLOAD_LOGS_ERROR: 'Bluetooth/downloadLogsError',
  DOWNLOAD_LOGS_COMPLETE: 'Bluetooth/downloadLogsComplete',
  SENSOR_DOWNLOAD_START: 'Bluetooth/sensorDownloadStart',
  SENSOR_DOWNLOAD_SUCCESS: 'Bluetooth/sensorDownloadSuccess',
  SENSOR_DOWNLOAD_ERROR: 'Bluetooth/sensorDownloadError',
};

const downloadLogsStart = () => ({ type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_START });
const downloadLogsComplete = () => ({ type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_COMPLETE });
const downloadLogsError = error => ({
  type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_ERROR,
  payload: { error },
});

const sensorDownloadStart = sensor => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_START,
  payload: { sensor },
});

const sensorDownloadError = (sensor, error) => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_ERROR,
  payload: { sensor, error },
});

const sensorDownloadSuccess = sensor => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_SUCCESS,
  payload: { sensor },
});

const downloadAll = () => async dispatch => {
  dispatch(downloadLogsStart());
  // Ensure there are some sensors which have been assigned a location before syncing.
  const sensors = UIDatabase.objects('Sensor').filtered('location != null && isActive == true');

  if (!sensors.length) {
    dispatch(downloadLogsError(syncStrings.no_sensors));
    return null;
  }

  for (let i = 0; i < sensors.length; i++) {
    const sensor = sensors[i];
    if (isValidMacAddress(sensors[i].macAddress)) {
      // Intentionally sequential
      try {
        // eslint-disable-next-line no-await-in-loop
        await dispatch(downloadLogsFromSensor(sensor));
        // eslint-disable-next-line no-await-in-loop
        await dispatch(downloadInfoFromSensor(sensor));
      } catch (exception) {
        dispatch(downloadLogsError(exception.message));
      }
    } else {
      dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_INVALID_MAC_FORMAT));
    }
  }
  // this will now overwrite the error message
  dispatch(downloadLogsComplete());
  return null;
};

const downloadLogsFromSensor = sensor => async dispatch => {
  try {
    const { macAddress, logInterval, logDelay, isPaused } = sensor;

    const timeNow = moment();

    if (timeNow.isAfter(moment(logDelay)) && !isPaused) {
      dispatch(sensorDownloadStart(sensor));

      try {
        const downloadedLogsResult =
          (await BleService().downloadLogsWithRetries(
            macAddress,
            VACCINE_CONSTANTS.MAX_BLUETOOTH_COMMAND_ATTEMPTS
          )) ?? {};

        if (downloadedLogsResult) {
          try {
            const savedTemperatureLogs = UIDatabase.objects(VACCINE_ENTITIES.TEMPERATURE_LOG)
              .filtered('sensor.macAddress == $0', macAddress)
              .sorted('timestamp', true);

            const [mostRecentLog] = savedTemperatureLogs;
            const mostRecentLogTime = mostRecentLog ? mostRecentLog.timestamp : null;
            const nextPossibleLogTime = mostRecentLogTime
              ? moment(mostRecentLogTime).add(logInterval, 's')
              : 0;

            const numberOfLogsToSave = await TemperatureLogManager().calculateNumberOfLogsToSave(
              logInterval,
              nextPossibleLogTime
            );

            const temperatureLogs = await TemperatureLogManager().createLogs(
              downloadedLogsResult,
              sensor,
              numberOfLogsToSave,
              mostRecentLogTime
            );

            await TemperatureLogManager().saveLogs(temperatureLogs);
            await dispatch(BreachActions.createConsecutiveBreaches(sensor));
          } catch (e) {
            dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_CANT_CONNECT));
          }
        }
      } catch (e) {
        dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_CANT_SAVE));
      }

      dispatch(sensorDownloadSuccess(sensor));
    }
  } catch (error) {
    dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_UNKNOWN));
  }
};

const downloadInfoFromSensor = sensor => async () => {
  const { macAddress } = sensor;
  const sensorInfo =
    (await BleService().getInfoWithRetries(
      macAddress,
      VACCINE_CONSTANTS.MAX_BLUETOOTH_COMMAND_ATTEMPTS
    )) ?? {};

  if (sensorInfo) {
    // creating a new object to allow mutating.
    // cannot use spread operator down here
    const updatedSensor = SensorManager().sensorCreator(sensor);
    updatedSensor.batteryLevel = parseInt(sensorInfo.batteryLevel, 10);

    await SensorManager().saveSensor(updatedSensor);
  }

  return null;
};

const startDownloadAll = () => async (dispatch, getState) => {
  // Ensure there isn't already a download in progress before starting a new one
  const state = getState();
  const isSyncingTemps = selectIsSyncingTemps(state);
  if (isSyncingTemps) return null;

  await PermissionActions.withLocationAndBluetooth(dispatch, getState, downloadAll());
  return null;
};

export const SensorDownloadActions = {
  startDownloadAll,
};
<commit_msg>Add casting to a unix timestsamp<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2021
 */

import moment from 'moment';
import { PermissionActions } from '../PermissionActions';
import BleService from '../../bluetooth/BleService';
import TemperatureLogManager from '../../bluetooth/TemperatureLogManager';
import SensorManager from '../../bluetooth/SensorManager';
import { UIDatabase } from '../../database';
import { isValidMacAddress, VACCINE_CONSTANTS } from '../../utilities/modules/vaccines/index';
import {
  DOWNLOADING_ERROR_CODES,
  VACCINE_ENTITIES,
} from '../../utilities/modules/vaccines/constants';
import { syncStrings } from '../../localization';
import { selectIsSyncingTemps } from '../../selectors/Bluetooth/sensorDownload';
import { BreachActions } from '../BreachActions';

export const DOWNLOAD_ACTIONS = {
  DOWNLOAD_LOGS_START: 'Bluetooth/downloadLogsStart',
  DOWNLOAD_LOGS_ERROR: 'Bluetooth/downloadLogsError',
  DOWNLOAD_LOGS_COMPLETE: 'Bluetooth/downloadLogsComplete',
  SENSOR_DOWNLOAD_START: 'Bluetooth/sensorDownloadStart',
  SENSOR_DOWNLOAD_SUCCESS: 'Bluetooth/sensorDownloadSuccess',
  SENSOR_DOWNLOAD_ERROR: 'Bluetooth/sensorDownloadError',
};

const downloadLogsStart = () => ({ type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_START });
const downloadLogsComplete = () => ({ type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_COMPLETE });
const downloadLogsError = error => ({
  type: DOWNLOAD_ACTIONS.DOWNLOAD_LOGS_ERROR,
  payload: { error },
});

const sensorDownloadStart = sensor => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_START,
  payload: { sensor },
});

const sensorDownloadError = (sensor, error) => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_ERROR,
  payload: { sensor, error },
});

const sensorDownloadSuccess = sensor => ({
  type: DOWNLOAD_ACTIONS.SENSOR_DOWNLOAD_SUCCESS,
  payload: { sensor },
});

const downloadAll = () => async dispatch => {
  dispatch(downloadLogsStart());
  // Ensure there are some sensors which have been assigned a location before syncing.
  const sensors = UIDatabase.objects('Sensor').filtered('location != null && isActive == true');

  if (!sensors.length) {
    dispatch(downloadLogsError(syncStrings.no_sensors));
    return null;
  }

  for (let i = 0; i < sensors.length; i++) {
    const sensor = sensors[i];
    if (isValidMacAddress(sensors[i].macAddress)) {
      // Intentionally sequential
      try {
        // eslint-disable-next-line no-await-in-loop
        await dispatch(downloadLogsFromSensor(sensor));
        // eslint-disable-next-line no-await-in-loop
        await dispatch(downloadInfoFromSensor(sensor));
      } catch (exception) {
        dispatch(downloadLogsError(exception.message));
      }
    } else {
      dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_INVALID_MAC_FORMAT));
    }
  }
  // this will now overwrite the error message
  dispatch(downloadLogsComplete());
  return null;
};

const downloadLogsFromSensor = sensor => async dispatch => {
  try {
    const { macAddress, logInterval, logDelay, isPaused } = sensor;

    const timeNow = moment();

    if (timeNow.isAfter(moment(logDelay)) && !isPaused) {
      dispatch(sensorDownloadStart(sensor));

      try {
        const downloadedLogsResult =
          (await BleService().downloadLogsWithRetries(
            macAddress,
            VACCINE_CONSTANTS.MAX_BLUETOOTH_COMMAND_ATTEMPTS
          )) ?? {};

        if (downloadedLogsResult) {
          try {
            const savedTemperatureLogs = UIDatabase.objects(VACCINE_ENTITIES.TEMPERATURE_LOG)
              .filtered('sensor.macAddress == $0', macAddress)
              .sorted('timestamp', true);

            const [mostRecentLog] = savedTemperatureLogs;
            const mostRecentLogTime = mostRecentLog ? mostRecentLog.timestamp : null;
            const nextPossibleLogTime = mostRecentLogTime
              ? moment(mostRecentLogTime).add(logInterval, 's').unix()
              : 0;

            const numberOfLogsToSave = await TemperatureLogManager().calculateNumberOfLogsToSave(
              logInterval,
              nextPossibleLogTime
            );

            const temperatureLogs = await TemperatureLogManager().createLogs(
              downloadedLogsResult,
              sensor,
              numberOfLogsToSave,
              mostRecentLogTime
            );

            await TemperatureLogManager().saveLogs(temperatureLogs);
            await dispatch(BreachActions.createConsecutiveBreaches(sensor));
          } catch (e) {
            dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_CANT_CONNECT));
          }
        }
      } catch (e) {
        dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_CANT_SAVE));
      }

      dispatch(sensorDownloadSuccess(sensor));
    }
  } catch (error) {
    dispatch(sensorDownloadError(sensor, DOWNLOADING_ERROR_CODES.E_UNKNOWN));
  }
};

const downloadInfoFromSensor = sensor => async () => {
  const { macAddress } = sensor;
  const sensorInfo =
    (await BleService().getInfoWithRetries(
      macAddress,
      VACCINE_CONSTANTS.MAX_BLUETOOTH_COMMAND_ATTEMPTS
    )) ?? {};

  if (sensorInfo) {
    // creating a new object to allow mutating.
    // cannot use spread operator down here
    const updatedSensor = SensorManager().sensorCreator(sensor);
    updatedSensor.batteryLevel = parseInt(sensorInfo.batteryLevel, 10);

    await SensorManager().saveSensor(updatedSensor);
  }

  return null;
};

const startDownloadAll = () => async (dispatch, getState) => {
  // Ensure there isn't already a download in progress before starting a new one
  const state = getState();
  const isSyncingTemps = selectIsSyncingTemps(state);
  if (isSyncingTemps) return null;

  await PermissionActions.withLocationAndBluetooth(dispatch, getState, downloadAll());
  return null;
};

export const SensorDownloadActions = {
  startDownloadAll,
};
<|endoftext|>


<commit_before>/**
  @module ember-flexberry
*/

import Ember from 'ember';
import LimitedRouteMixin from '../mixins/limited-route';
import SortableRouteMixin from '../mixins/sortable-route';
import PaginatedRouteMixin from '../mixins/paginated-route';
import ProjectedModelFormRoute from '../routes/projected-model-form';
import FlexberryObjectlistviewRouteMixin from '../mixins/flexberry-objectlistview-route';
import FlexberryObjectlistviewHierarchicalRouteMixin from '../mixins/flexberry-objectlistview-hierarchical-route';
import ReloadListMixin from '../mixins/reload-list-mixin';

/**
  Base route for the List Forms.

  This class re-exports to the application as `/routes/list-form`.
  So, you can inherit from `./list-form`, even if file `app/routes/list-form.js` is not presented in the application.

  @example
    ```javascript
    // app/routes/employees.js
    import ListFormRoute from './list-form';
    export default ListFormRoute.extend({
    });
    ```

    If you want to add some common logic on all List Forms, you can override `app/routes/list-form.js` as follows:
    ```javascript
    // app/routes/list-form.js
    import ListFormRoute from 'ember-flexberry/routes/list-form';
    export default ListFormRoute.extend({
    });
    ```

  @class ListFormRoute
  @extends ProjectedModelFormRoute
  @uses PaginatedRouteMixin
  @uses SortableRouteMixin
  @uses LimitedRouteMixin
  @uses ReloadListMixin
  @uses FlexberryObjectlistviewRouteMixin
*/
export default ProjectedModelFormRoute.extend(
PaginatedRouteMixin,
SortableRouteMixin,
LimitedRouteMixin,
ReloadListMixin,
FlexberryObjectlistviewRouteMixin,
FlexberryObjectlistviewHierarchicalRouteMixin, {
  /**
    Link on {{#crossLink FormLoadTimeTrackerService}}{{/crossLink}}.

    @property formLoadTimeTracker
    @type FormLoadTimeTrackerService
    @private
  */
  formLoadTimeTracker: Ember.inject.service(),

  /**
    Current sorting.

    @property sorting
    @type Array
    @default []
  */
  sorting: [],

  /**
    @property colsConfigMenu
    @type Service
  */
  colsConfigMenu: Ember.inject.service(),

  /**
    Service that triggers objectlistview events.

    @property objectlistviewEventsService
    @type Service
  */
  objectlistviewEventsService: Ember.inject.service('objectlistview-events'),

  /**
    A hook you can implement to convert the URL into the model for this route.
    [More info](http://emberjs.com/api/classes/Ember.Route.html#method_model).

    @method model
    @param {Object} params
    @param {Object} transition
  */
  model: function(params, transition) {
    this.get('formLoadTimeTracker').set('startLoadTime', performance.now());

    let controller = this.controllerFor(this.routeName);
    this.get('objectlistviewEventsService').setLoadingState('loading');

    let modelName = this.get('modelName');
    let webPage = transition.targetName;
    let projectionName = this.get('modelProjection');
    let filtersPredicate = this._filtersPredicate();
    let limitPredicate =
      this.objectListViewLimitPredicate({ modelName: modelName, projectionName: projectionName, params: params });
    let userSettingsService = this.get('userSettingsService');
    userSettingsService.setCurrentWebPage(webPage);
    let developerUserSettings = this.get('developerUserSettings');
    Ember.assert('Property developerUserSettings is not defined in /app/routes/' + transition.targetName + '.js', developerUserSettings);

    let nComponents = 0;
    let componentName;
    for (componentName in developerUserSettings) {
      let componentDesc = developerUserSettings[componentName];
      switch (typeof componentDesc) {
        case 'string':
          developerUserSettings[componentName] = JSON.parse(componentDesc);
          break;
        case 'object':
          break;
        default:
          Ember.assert('Component description ' + 'developerUserSettings.' + componentName +
            'in /app/routes/' + transition.targetName + '.js must have types object or string', false);
      }
      nComponents += 1;
    }

    if (nComponents === 0) {
      Ember.assert('Developer MUST DEFINE component settings in /app/routes/' + transition.targetName + '.js', false);
    }

    Ember.assert('Developer MUST DEFINE SINGLE components settings in /app/routes/' + transition.targetName + '.js' + nComponents + ' defined.',
      nComponents === 1);
    userSettingsService.setDefaultDeveloperUserSettings(developerUserSettings);
    let userSettingPromise = userSettingsService.setDeveloperUserSettings(developerUserSettings);
    let listComponentNames = userSettingsService.getListComponentNames();
    componentName = listComponentNames[0];
    userSettingPromise
      .then(currectPageUserSettings => {
        if (params) {
          userSettingsService.setCurrentParams(componentName, params);
        }

        let hierarchicalAttribute;
        if (controller.get('inHierarchicalMode')) {
          hierarchicalAttribute = controller.get('hierarchicalAttribute');
        }

        this.sorting = userSettingsService.getCurrentSorting(componentName);
        this.perPage = userSettingsService.getCurrentPerPage(componentName);
        if (this.perPage !== params.perPage) {
          if (params.perPage !== 5) {
            this.perPage = params.perPage;
            userSettingsService.setCurrentPerPage(componentName, undefined, this.perPage);
          } else {
            if (this.sorting.length === 0) {
              this.transitionTo(this.currentRouteName, { queryParams: { sort: null, perPage: this.perPage || 5 } }); // Show page without sort parameters
            } else {
              this.transitionTo(this.currentRouteName, { queryParams: { perPage: this.perPage || 5 } });  //Reload current page and records (model) list
            }
          }
        }

        let queryParameters = {
          modelName: modelName,
          projectionName: projectionName,
          perPage: this.perPage,
          page: params.page,
          sorting: this.sorting,
          filter: params.filter,
          filterCondition: this.get('controller.filterCondition'),
          filters: filtersPredicate,
          predicate: limitPredicate,
          hierarchicalAttribute: hierarchicalAttribute,
        };

        this.onModelLoadingStarted(queryParameters);
        this.get('colsConfigMenu').updateNamedSettingTrigger();

        // Find by query is always fetching.
        // TODO: support getting from cache with "store.all->filterByProjection".
        // TODO: move includeSorting to setupController mixins?
        return this.reloadList(queryParameters);
      }).then((records) => {
        this.get('formLoadTimeTracker').set('endLoadTime', performance.now());
        this.onModelLoadingFulfilled(records);
        this.includeSorting(records, this.sorting);
        this.get('controller').set('model', records);
        return records;
      }).catch((errorData) => {
        this.onModelLoadingRejected(errorData);
      }).finally((data) => {
        this.onModelLoadingAlways(data);
        if (this.get('objectlistviewEventsService.loadingState') === 'loading') {
          this.get('objectlistviewEventsService').setLoadingState('');
        }
      });

    if (this.get('controller') === undefined) {
      return { isLoading: true };
    }

    // TODO: Check controller loaded model loading parameters and return it without reloading if there is same backend query was executed.
    let model = this.get('controller.model');

    if (model !== null) {
      return model;
    } else {
      return { isLoading: true };
    }
  },

  actions: {
    /**
      Event handler for processing promise model rejecting.
      [More info](https://emberjs.com/api/ember/2.4/classes/Ember.Route/events/error?anchor=error).

      @method actions.error
      @param {Object} error
      @param {Object} transition
    */
    error: function(error, transition) {
        this.onModelLoadingRejected(error);
    }
  },

  /**
    This method will be invoked before model loading operation will be called.
    Override this method to add some custom logic on model loading operation start.

    @example
      ```javascript
      onModelLoadingStarted(queryParameters) {
        alert('Model loading operation started!');
      }
      ```
    @method onModelLoadingStarted.
    @param {Object} queryParameters Query parameters used for model loading operation.
  */
  onModelLoadingStarted(queryParameters) {
  },

  /**
    This method will be invoked when model loading operation successfully completed.
    Override this method to add some custom logic on model loading operation success.

    @example
      ```javascript
      onModelLoadingFulfilled() {
        alert('Model loading operation succeed!');
      }
      ```
    @method onModelLoadingFulfilled.
    @param {Object} model Loaded model data.
  */
  onModelLoadingFulfilled(model) {
  },

  /**
    This method will be invoked when model loading operation completed, but failed.
    Override this method to add some custom logic on model loading operation fail.
    By default showing error form.

    @example
      ```javascript
      onModelLoadingRejected() {
        alert('Model loading operation failed!');
      }
      ```
    @method onModelLoadingRejected.
    @param {Object} errorData Data about model loading operation fail.
  */
  onModelLoadingRejected(errorData) {
    if (!this.get('controller') || !this.get('controller.model') || this.get('controller.model.isLoading')) {
      this.get('objectlistviewEventsService').setLoadingState('error');
      errorData = errorData || {};
      errorData.retryRoute = this.routeName;
      this.intermediateTransitionTo('error', errorData);
    } else {
      this.controller.send('error', errorData);
    }
  },

  /**
    This method will be invoked always when model loading operation completed,
    regardless of model loading promise's state (was it fulfilled or rejected).
    Override this method to add some custom logic on model loading operation completion.

    @example
      ```js
      onModelLoadingAlways(data) {
        alert('Model loading operation completed!');
      }
      ```

    @method onModelLoadingAlways.
    @param {Object} data Data about completed model loading operation.
  */
  onModelLoadingAlways(data) {
  },

  /**
    A hook you can use to setup the controller for the current route.
    [More info](http://emberjs.com/api/classes/Ember.Route.html#method_setupController).

    @method setupController
    @param {Ember.Controller} controller
    @param {Object} model
  */
  setupController: function(controller, model) {
    this._super(...arguments);
    this.get('formLoadTimeTracker').set('startRenderTime', performance.now());

    // Define 'modelProjection' for controller instance.
    // TODO: remove that when list-form controller will be moved to this route.
    let modelClass = this.store.modelFor(this.get('modelName'));
    let proj = modelClass.projections.get(this.get('modelProjection'));
    controller.set('userSettings', this.userSettings);
    controller.set('modelProjection', proj);
    controller.set('developerUserSettings', this.get('developerUserSettings'));
    controller.set('resultPredicate', this.get('resultPredicate'));
    if (Ember.isNone(controller.get('defaultDeveloperUserSettings'))) {
      controller.set('defaultDeveloperUserSettings', Ember.$.extend(true, {}, this.get('developerUserSettings')));
    }
  }
});
<commit_msg>Fix JSCS<commit_after>/**
  @module ember-flexberry
*/

import Ember from 'ember';
import LimitedRouteMixin from '../mixins/limited-route';
import SortableRouteMixin from '../mixins/sortable-route';
import PaginatedRouteMixin from '../mixins/paginated-route';
import ProjectedModelFormRoute from '../routes/projected-model-form';
import FlexberryObjectlistviewRouteMixin from '../mixins/flexberry-objectlistview-route';
import FlexberryObjectlistviewHierarchicalRouteMixin from '../mixins/flexberry-objectlistview-hierarchical-route';
import ReloadListMixin from '../mixins/reload-list-mixin';

/**
  Base route for the List Forms.

  This class re-exports to the application as `/routes/list-form`.
  So, you can inherit from `./list-form`, even if file `app/routes/list-form.js` is not presented in the application.

  @example
    ```javascript
    // app/routes/employees.js
    import ListFormRoute from './list-form';
    export default ListFormRoute.extend({
    });
    ```

    If you want to add some common logic on all List Forms, you can override `app/routes/list-form.js` as follows:
    ```javascript
    // app/routes/list-form.js
    import ListFormRoute from 'ember-flexberry/routes/list-form';
    export default ListFormRoute.extend({
    });
    ```

  @class ListFormRoute
  @extends ProjectedModelFormRoute
  @uses PaginatedRouteMixin
  @uses SortableRouteMixin
  @uses LimitedRouteMixin
  @uses ReloadListMixin
  @uses FlexberryObjectlistviewRouteMixin
*/
export default ProjectedModelFormRoute.extend(
PaginatedRouteMixin,
SortableRouteMixin,
LimitedRouteMixin,
ReloadListMixin,
FlexberryObjectlistviewRouteMixin,
FlexberryObjectlistviewHierarchicalRouteMixin, {
  /**
    Link on {{#crossLink FormLoadTimeTrackerService}}{{/crossLink}}.

    @property formLoadTimeTracker
    @type FormLoadTimeTrackerService
    @private
  */
  formLoadTimeTracker: Ember.inject.service(),

  /**
    Current sorting.

    @property sorting
    @type Array
    @default []
  */
  sorting: [],

  /**
    @property colsConfigMenu
    @type Service
  */
  colsConfigMenu: Ember.inject.service(),

  /**
    Service that triggers objectlistview events.

    @property objectlistviewEventsService
    @type Service
  */
  objectlistviewEventsService: Ember.inject.service('objectlistview-events'),

  /**
    A hook you can implement to convert the URL into the model for this route.
    [More info](http://emberjs.com/api/classes/Ember.Route.html#method_model).

    @method model
    @param {Object} params
    @param {Object} transition
  */
  model: function(params, transition) {
    this.get('formLoadTimeTracker').set('startLoadTime', performance.now());

    let controller = this.controllerFor(this.routeName);
    this.get('objectlistviewEventsService').setLoadingState('loading');

    let modelName = this.get('modelName');
    let webPage = transition.targetName;
    let projectionName = this.get('modelProjection');
    let filtersPredicate = this._filtersPredicate();
    let limitPredicate =
      this.objectListViewLimitPredicate({ modelName: modelName, projectionName: projectionName, params: params });
    let userSettingsService = this.get('userSettingsService');
    userSettingsService.setCurrentWebPage(webPage);
    let developerUserSettings = this.get('developerUserSettings');
    Ember.assert('Property developerUserSettings is not defined in /app/routes/' + transition.targetName + '.js', developerUserSettings);

    let nComponents = 0;
    let componentName;
    for (componentName in developerUserSettings) {
      let componentDesc = developerUserSettings[componentName];
      switch (typeof componentDesc) {
        case 'string':
          developerUserSettings[componentName] = JSON.parse(componentDesc);
          break;
        case 'object':
          break;
        default:
          Ember.assert('Component description ' + 'developerUserSettings.' + componentName +
            'in /app/routes/' + transition.targetName + '.js must have types object or string', false);
      }
      nComponents += 1;
    }

    if (nComponents === 0) {
      Ember.assert('Developer MUST DEFINE component settings in /app/routes/' + transition.targetName + '.js', false);
    }

    Ember.assert('Developer MUST DEFINE SINGLE components settings in /app/routes/' + transition.targetName + '.js' + nComponents + ' defined.',
      nComponents === 1);
    userSettingsService.setDefaultDeveloperUserSettings(developerUserSettings);
    let userSettingPromise = userSettingsService.setDeveloperUserSettings(developerUserSettings);
    let listComponentNames = userSettingsService.getListComponentNames();
    componentName = listComponentNames[0];
    userSettingPromise
      .then(currectPageUserSettings => {
        if (params) {
          userSettingsService.setCurrentParams(componentName, params);
        }

        let hierarchicalAttribute;
        if (controller.get('inHierarchicalMode')) {
          hierarchicalAttribute = controller.get('hierarchicalAttribute');
        }

        this.sorting = userSettingsService.getCurrentSorting(componentName);
        this.perPage = userSettingsService.getCurrentPerPage(componentName);
        if (this.perPage !== params.perPage) {
          if (params.perPage !== 5) {
            this.perPage = params.perPage;
            userSettingsService.setCurrentPerPage(componentName, undefined, this.perPage);
          } else {
            if (this.sorting.length === 0) {
              this.transitionTo(this.currentRouteName, { queryParams: { sort: null, perPage: this.perPage || 5 } }); // Show page without sort parameters
            } else {
              this.transitionTo(this.currentRouteName, { queryParams: { perPage: this.perPage || 5 } });  //Reload current page and records (model) list
            }
          }
        }

        let queryParameters = {
          modelName: modelName,
          projectionName: projectionName,
          perPage: this.perPage,
          page: params.page,
          sorting: this.sorting,
          filter: params.filter,
          filterCondition: this.get('controller.filterCondition'),
          filters: filtersPredicate,
          predicate: limitPredicate,
          hierarchicalAttribute: hierarchicalAttribute,
        };

        this.onModelLoadingStarted(queryParameters);
        this.get('colsConfigMenu').updateNamedSettingTrigger();

        // Find by query is always fetching.
        // TODO: support getting from cache with "store.all->filterByProjection".
        // TODO: move includeSorting to setupController mixins?
        return this.reloadList(queryParameters);
      }).then((records) => {
        this.get('formLoadTimeTracker').set('endLoadTime', performance.now());
        this.onModelLoadingFulfilled(records);
        this.includeSorting(records, this.sorting);
        this.get('controller').set('model', records);
        return records;
      }).catch((errorData) => {
        this.onModelLoadingRejected(errorData);
      }).finally((data) => {
        this.onModelLoadingAlways(data);
        if (this.get('objectlistviewEventsService.loadingState') === 'loading') {
          this.get('objectlistviewEventsService').setLoadingState('');
        }
      });

    if (this.get('controller') === undefined) {
      return { isLoading: true };
    }

    // TODO: Check controller loaded model loading parameters and return it without reloading if there is same backend query was executed.
    let model = this.get('controller.model');

    if (model !== null) {
      return model;
    } else {
      return { isLoading: true };
    }
  },

  actions: {
    /**
      Event handler for processing promise model rejecting.
      [More info](https://emberjs.com/api/ember/2.4/classes/Ember.Route/events/error?anchor=error).

      @method actions.error
      @param {Object} error
      @param {Object} transition
    */
    error: function(error, transition) {
      this.onModelLoadingRejected(error);
    }
  },

  /**
    This method will be invoked before model loading operation will be called.
    Override this method to add some custom logic on model loading operation start.

    @example
      ```javascript
      onModelLoadingStarted(queryParameters) {
        alert('Model loading operation started!');
      }
      ```
    @method onModelLoadingStarted.
    @param {Object} queryParameters Query parameters used for model loading operation.
  */
  onModelLoadingStarted(queryParameters) {
  },

  /**
    This method will be invoked when model loading operation successfully completed.
    Override this method to add some custom logic on model loading operation success.

    @example
      ```javascript
      onModelLoadingFulfilled() {
        alert('Model loading operation succeed!');
      }
      ```
    @method onModelLoadingFulfilled.
    @param {Object} model Loaded model data.
  */
  onModelLoadingFulfilled(model) {
  },

  /**
    This method will be invoked when model loading operation completed, but failed.
    Override this method to add some custom logic on model loading operation fail.
    By default showing error form.

    @example
      ```javascript
      onModelLoadingRejected() {
        alert('Model loading operation failed!');
      }
      ```
    @method onModelLoadingRejected.
    @param {Object} errorData Data about model loading operation fail.
  */
  onModelLoadingRejected(errorData) {
    if (!this.get('controller') || !this.get('controller.model') || this.get('controller.model.isLoading')) {
      this.get('objectlistviewEventsService').setLoadingState('error');
      errorData = errorData || {};
      errorData.retryRoute = this.routeName;
      this.intermediateTransitionTo('error', errorData);
    } else {
      this.controller.send('error', errorData);
    }
  },

  /**
    This method will be invoked always when model loading operation completed,
    regardless of model loading promise's state (was it fulfilled or rejected).
    Override this method to add some custom logic on model loading operation completion.

    @example
      ```js
      onModelLoadingAlways(data) {
        alert('Model loading operation completed!');
      }
      ```

    @method onModelLoadingAlways.
    @param {Object} data Data about completed model loading operation.
  */
  onModelLoadingAlways(data) {
  },

  /**
    A hook you can use to setup the controller for the current route.
    [More info](http://emberjs.com/api/classes/Ember.Route.html#method_setupController).

    @method setupController
    @param {Ember.Controller} controller
    @param {Object} model
  */
  setupController: function(controller, model) {
    this._super(...arguments);
    this.get('formLoadTimeTracker').set('startRenderTime', performance.now());

    // Define 'modelProjection' for controller instance.
    // TODO: remove that when list-form controller will be moved to this route.
    let modelClass = this.store.modelFor(this.get('modelName'));
    let proj = modelClass.projections.get(this.get('modelProjection'));
    controller.set('userSettings', this.userSettings);
    controller.set('modelProjection', proj);
    controller.set('developerUserSettings', this.get('developerUserSettings'));
    controller.set('resultPredicate', this.get('resultPredicate'));
    if (Ember.isNone(controller.get('defaultDeveloperUserSettings'))) {
      controller.set('defaultDeveloperUserSettings', Ember.$.extend(true, {}, this.get('developerUserSettings')));
    }
  }
});
<|endoftext|>


<commit_before>/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for t`he specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const config = {
  context: __dirname,

  entry: './src/App.jsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './public')
  },
  devtool: 'cheap-module-source-map',
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: true
  },
  plugins: [new ExtractTextPlugin('./bundle.css')],
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.jsx?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        include: [path.resolve('frontend'), path.resolve('node_modules/preact-compat/src')],
        query: {
          babelrc: false,
          presets: [
            "react",
            ["env", {
              "targets": {
                "browsers": "last 2 versions"
              },
              "loose": true,
              "modules": "commonjs"
            }]
          ],
          "plugins": [
            "transform-decorators",
            "transform-class-properties",
            "transform-object-rest-spread"
          ]
        }
      },
      {
        test: /\.css$/,
        exclude: [/\.global\./, /node_modules/],
        loader: ExtractTextPlugin.extract(
          {
            fallback: 'style-loader',
            use:[
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 1,
                  modules: true,
                  autoprefixer: true,
                  minimize: true,
                  localIdentName: '[name]__[local]___[hash:base64:5]'
                }
              }
            ]
          })
      },
      {
        test: /\.css/,
        include: [/\.global\./, /node_modules/],
        loader: ExtractTextPlugin.extract(
          {
            fallback: 'style-loader',
            use:[
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 1,
                  modules: false,
                  minimize: true
                }
              }
            ]
          })
      },
      {/* Workaround for issue https://github.com/firebase/firebaseui-web/issues/163 */},
      {
        test: /npm\.js$/,
        loader: 'string-replace-loader',
        include: path.resolve('node_modules/firebaseui/dist'),
        query: {
          search: 'require(\'firebase\');',
          replace: 'require(\'firebase/app\');require(\'firebase/auth\')',
        }
      }
    ]
  }
};

console.log('Packing for', process.env.NODE_ENV || 'development');

if (process.env.NODE_ENV === 'production') {
  config.devtool = 'source-map';
}

module.exports = config;
<commit_msg>Build fix for sample<commit_after>/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for t`he specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const config = {
  context: __dirname,

  entry: './src/App.jsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './public')
  },
  devtool: 'cheap-module-source-map',
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: true
  },
  plugins: [new ExtractTextPlugin('./bundle.css')],
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        include: [path.resolve('src')],
        query: {
          babelrc: false,
          presets: [
            "react",
            ["env", {
              "targets": {
                "browsers": "last 2 versions"
              },
              "loose": true,
              "modules": "commonjs"
            }]
          ],
          "plugins": [
            "transform-decorators",
            "transform-class-properties",
            "transform-object-rest-spread"
          ]
        }
      },
      {
        test: /\.css$/,
        exclude: [/\.global\./, /node_modules/],
        loader: ExtractTextPlugin.extract(
          {
            fallback: 'style-loader',
            use:[
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 1,
                  modules: true,
                  autoprefixer: true,
                  minimize: true,
                  localIdentName: '[name]__[local]___[hash:base64:5]'
                }
              }
            ]
          })
      },
      {
        test: /\.css/,
        include: [/\.global\./, /node_modules/],
        loader: ExtractTextPlugin.extract(
          {
            fallback: 'style-loader',
            use:[
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 1,
                  modules: false,
                  minimize: true
                }
              }
            ]
          })
      }
    ]
  }
};

console.log('Packing for', process.env.NODE_ENV || 'development');

if (process.env.NODE_ENV === 'production') {
  config.devtool = 'source-map';
}

module.exports = config;
<|endoftext|>


<commit_before>var Async = require('async');
var Code = require('code');
var Lab = require('lab');
var Pool = require('../');

var lab = exports.lab = Lab.script();
var expect = Code.expect;

lab.experiment('PooledSandbox', function () {
    lab.test('should run a Function instance', function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });
        var args = [1, 2];

        pool.run({ code: userCode, args: args}, function (err, response) {
            expect(err).to.be.null();
            expect(response.data).to.be.an.array();
            expect(response.data).to.deep.equal(args);

            done();
        });

        function userCode(arg1, arg2, cb) {
            cb(null, arg1, arg2);
        }
    });

    lab.test('should run a Function instance many times', function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });

        Async.times(50, runCodeInPool, function (err) {
            pool.destroy(done);
        });

        function runCodeInPool(n, next) {
            pool.run({ code: userCode, args: [n]}, function (err, response) {
                expect(err).to.be.null();
                expect(response.data).to.be.an.array();
                expect(response.data).to.deep.equal([n]);

                next();
            });
        }

        function userCode(n, cb) {
            cb(null, n);
        }
    });

    lab.test('should handle a blocked event loop', { timeout: 6000 }, function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 5000, tripwireTimeout: 100, stdout: process.stdout, stderr: process.stderr }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxUnhandledError);
            expect(err.message).to.match(/blocked/);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            while(true) {}
        }
    });

    lab.test('should handle code that never calls the callback', function (done) {
        var pool = new Pool();
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 1000 }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxTimeoutError);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            console.log('Do nothing, quickly.');
        }
    });

    lab.test('should handle code that throws an uncaught exception', function (done) {
        var pool = new Pool();
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 1000 }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxUnhandledError);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            throw 'a hissy fit';
        }
    });
});<commit_msg>Make tests assume SandboxTimeoutError instead of SandboxUnhandledError for blocked event loop<commit_after>var Async = require('async');
var Code = require('code');
var Lab = require('lab');
var Pool = require('../');

var lab = exports.lab = Lab.script();
var expect = Code.expect;

lab.experiment('PooledSandbox', function () {
    lab.test('should run a Function instance', function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });
        var args = [1, 2];

        pool.run({ code: userCode, args: args}, function (err, response) {
            expect(err).to.be.null();
            expect(response.data).to.be.an.array();
            expect(response.data).to.deep.equal(args);

            done();
        });

        function userCode(arg1, arg2, cb) {
            cb(null, arg1, arg2);
        }
    });

    lab.test('should run a Function instance many times', function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });

        Async.times(50, runCodeInPool, function (err) {
            pool.destroy(done);
        });

        function runCodeInPool(n, next) {
            pool.run({ code: userCode, args: [n]}, function (err, response) {
                expect(err).to.be.null();
                expect(response.data).to.be.an.array();
                expect(response.data).to.deep.equal([n]);

                next();
            });
        }

        function userCode(n, cb) {
            cb(null, n);
        }
    });

    lab.test('should handle a blocked event loop', { timeout: 6000 }, function (done) {
        var pool = new Pool({ idleTimeoutMillis: 500 });
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 1000 }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxTimeoutError);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            while(true) {}
        }
    });

    lab.test('should handle code that never calls the callback', function (done) {
        var pool = new Pool();
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 1000 }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxTimeoutError);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            console.log('Do nothing, quickly.');
        }
    });

    lab.test('should handle code that throws an uncaught exception', function (done) {
        var pool = new Pool();
        var args = [];

        pool.run({ code: userCode, args: args, timeout: 1000 }, function (err, response) {
            expect(err).to.be.an.instanceof(Pool.SandboxUnhandledError);
            expect(response.data).to.not.exist();
            expect(response.stdio).to.be.an.object();

            pool.destroy(done);
        });

        function userCode(cb) {
            throw 'a hissy fit';
        }
    });
});<|endoftext|>