title: How to wait for a callback to be executed inside a AngularJS directive unit test?
body: <p>I have a simple directive that works fine (written with TypeScript but it does not really matter) that allows to get the content of files uploaded via <code>&lt;input type="file"&gt;</code> and HTML5 File API.</p>
  
  <p>The use is simple:</p>
  
  <pre><code>&lt;body ng-controller="MyCtrl as ctrl"&gt;
    &lt;input type="file" file-read="ctrl.readInputFile(data)"&gt;
  &lt;/body&gt;
  </code></pre>
  
  <p>callback readInputFile() is being called with the files content.</p>
  
  <pre><code>interface IFileReadScope extends angular.IScope {
    fileRead(data: any): void;
  }
  
  class FileRead implements angular.IDirective {
    restrict = 'A';
    scope = {
      fileRead: '&amp;'
    };
  
    link = (scope: IFileReadScope, element: angular.IAugmentedJQuery) =&gt; {
      element.on('change', (e: Event) =&gt; {
        const files: FileList = (&lt;HTMLInputElement&gt; e.target).files;
  
        for (let i = 0; i &lt; files.length; i++) {
          const file = files[i];
  
          const reader = new FileReader();
  
          reader.onload = (e: Event) =&gt; {
            scope.$apply(() =&gt; {
              scope.fileRead({data: (&lt;FileReader&gt; e.target).result});
            });
          };
  
          reader.readAsText(file);
        }
      });
    };
  
    static factory(): angular.IDirectiveFactory {
      const directive = () =&gt; new FileRead();
      return directive;
    }
  }
  
  app.directive('fileRead', FileRead.factory());
  </code></pre>
  
  <p>And here the test for it:</p>
  
  <pre><code>describe('fileRead', () =&gt; {
    beforeEach(module('app'));
  
    let $compile: angular.ICompileService;
    let scope: any;
  
    beforeEach(inject((_$compile_, _$rootScope_) =&gt; {
      $compile = _$compile_;
      scope = _$rootScope_.$new();
    }));
  
    function compileTemplate(template: string): angular.IAugmentedJQuery {
      const el = $compile(angular.element(template))(scope);
      scope.$digest();
      return el;
    }
  
    it('should call a callback each time a file has been read', () =&gt; {
      const el = compileTemplate(
        '&lt;input type="file" file-read="readInputFile(data)"&gt;'
      );
  
      const files = new Array&lt;string&gt;();
  
      scope.readInputFile = (data: any) =&gt; {
        files.push(data);
      };
  
      el.triggerHandler({
        type: 'change',
        target: {
          files: [
            new Blob(['data0']),
            new Blob(['data1'])
          ]
        }
      });
  
      scope.$digest();
  
      // Fails because it does not wait for scope.readInputFile() to be called
      expect(files.length).toEqual(2);
      expect(files[0]).toEqual('data0');
      expect(files[1]).toEqual('data1');
    });
  });
  </code></pre>
  
  <p>The problem is that I don't know how to wait for <code>scope.readInputFile()</code> callback to be executed.</p>
  
  <p>I've tried scope.$digest(), $rootScope.$digest(), $apply, Jasmine spyOn... I've also checked how popular directives do that. It seems like nobody do "that" :/</p>
  
  <p>Any idea?</p>
  
  <p>Edit:</p>
  
  <p>Thanks to Hooray Im Helping and Matho, here the 2 possible solutions using Jasmine 2:</p>
  
  <pre><code>let files: string[];
  
  beforeEach(done =&gt; {
    const el = compileTemplate(
      '&lt;input type="file" file-read="readInputFile(data)"&gt;'
    );
  
    files = new Array&lt;string&gt;();
  
    scope.readInputFile = (data: any) =&gt; {
      files.push(data);
      if (files.length === 2) done();
    };
  
    el.triggerHandler({
      type: 'change',
      target: {
        files: [
          new Blob(['data0']),
          new Blob(['data1'])
        ]
      }
    });
  
    scope.$digest();
  });
  
  it('should call a callback each time a file has been read', () =&gt; {
    expect(files.length).toEqual(2);
    expect(files[0]).toEqual('data0');
    expect(files[1]).toEqual('data1');
  });
  </code></pre>
  
  <p>And the more elegant one in this case:</p>
  
  <pre><code>it('should call a callback each time a file has been read', done =&gt; {
    const el = compileTemplate(
      '&lt;input type="file" file-read="readInputFile(data)"&gt;'
    );
  
    const files = new Array&lt;string&gt;();
  
    scope.readInputFile = (data: any) =&gt; {
      files.push(data);
      if (files.length === 2) {
        expect(files[0]).toEqual('data0');
        expect(files[1]).toEqual('data1');
        done();
      }
    };
  
    el.triggerHandler({
      type: 'change',
      target: {
        files: [
          new Blob(['data0']),
          new Blob(['data1'])
        ]
      }
    });
  
    scope.$digest();
  });
  </code></pre>
  
  <p>If <code>scope.fileRead()</code> is not executed inside the directive, then the test will fail:</p>
  
  <pre><code>FAILED fileRead should call a callback each time a file has been read
  Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
  </code></pre>
  
  <p>So if the code breaks the unit test will fail => job done :)</p>