Q:

Angular & Jasmine: how to test that a $q promise chain was resolved

I have a Service that expose a function that receives a parsed CSV (using papaparse) and promise that reflects the parsing status:
 If the file was missing mandatory fields, the promise is rejected
 Otherwise, It parses each row into an item and auto populates the missing fields (the auto population process is asynchronous).
 when all items are populated, the function resolves the promise with the items array 
The function I want to test is onCsvParse: 
angular.module('csvParser', [])
  .factory('csvParser', ['$http',
    function($http) {
      var service = {
        onCsvParse: function(results, creatingBulkItems) {
          var errors = this.getCsvErrors(results);
          if (errors.length > 0) {
            //reject
            creatingBulkItems.reject(errors);
          } else {

            var items = this.parseCsv(results);
            var autoPopulateItems = [],
              populatedItems = [];
            for (var i = 0; i < populatedItems.length; i++) {
              var item = items[i];
              if (item.name === "" /*or some any field is missing */ ) {
                // auto populate item
                autoPopulateItems.push(this.autoPopulateItem(item));
              } else {
                var populatedItem = $q.when(item);
                populatedItems.push(populatedItem);
              }
            }
            populatedItems =autoPopulateItems.concat(populatedItems);
            var populatingAllItems = $q.all(populatedItems);
            populatingAllItems.then(function(items) {
              creatingBulkItems.resolve(items);
            }, function(err) {
              creatingBulkItems.resolve(err);
            });
          }
        },
        autoPopulateItem: function(newItem) {
          var populatingItem = $q.defer();
          var item = angular.copy(newItem);
          $http.post('api/getItemData', { /*.....*/ })
            .success(function(response) {
              //----Populate item fields
              item.name = response.name;
              //....
              //resolve the promise
              populatingItem.resolve(item)

            }).error(err) {
              // resolving on error for $q.all indication
              populatingItem.resolve(item)
            };
          return populatingItem.promise;

        }
      }
      return service;
    }
  ])

My test for this method looks as follows (simplified): 
describe('bulk items upload test', function() {
  //upload csv & test scenarios...
  var $rootScope, $q, csvResults = {};
  var $httpBackend, requestHandler;
  beforeEach(module('csvParser'));

  beforeEach(inject(function(_$rootScope_, _$q_) {
    $rootScope = _$rootScope_;
    $q = _$q_;
  }));
  beforeEach(inject(function($injector) {
    // Set up the mock http service responses
    $httpBackend = $injector.get('$httpBackend');
    // backend definition common for all tests
    requestHandler = $httpBackend.when('POST', 'api/getItemData')
      .respond({
        name: "name",
        description: "description",
        imageUrl: "www.google.com"

      });

    //   afterEach(function(){ $rootScope.$apply();});

  }));
  it('Should parse csv string', function(done) {
    var csvString = "Name,Description of the page";//...

    Papa.parse(csvString, {
      complete: function(results) {
        csvResults = results;
        done();
      }
    });

  });
  it('Should fail', function(done) {
    var creatingBulkItems = $q.defer();
    console.log("here..");
    csvParser.onCsvParse(csvResults, creatingBulkItems);
    creatingBulkItems.promise.then(function() {
      console.log("1here..");
      //promise is never resolved
      expect(1).toEqual(1);
      done();
    }, function() {
      //promise is never rejeceted
      console.log("2here..");
      expect(1).toEqual(1);
      done();
    });
    $rootScope.$apply();

  });

});

With this I get the error: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. 
the promises are not resolved, although I called $rootScope.$apply() and I am also not calling a real asynchronous call (only mocks,except $q.all).
How can I make it work?

A:

After reading this article: https://gist.github.com/domenic/3889970 I found out my problem.
The key point is to flatten the promise chains using the promise.then return value.

This [promise.then] function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.

Instead of resolving the outer promise within the inner promise success/fail callbacks, the outer promise is resolved in the inner promise.then callbacks
So my fix is something like this:
onCsvParse: function(results) {
      var errors = this.getCsvErrors(results);
      if (errors.length > 0) {
         var deferred = $q.defer();
        //reject
        return deferred.reject(errors);
      } else {

        var items = this.parseCsv(results);
        var autoPopulateItems = [],
          populatedItems = [];
        for (var i = 0; i < populatedItems.length; i++) {
          var item = items[i];
          if (item.name === "" /*or some any field is missing */ ) {
            // auto populate item
            autoPopulateItems.push(this.autoPopulateItem(item));
          } else {
            var populatedItem = $q.when(item);
            populatedItems.push(populatedItem);
          }
        }
        populatedItems = autoPopulateItems.concat(populatedItems);
        var populatingAllItems = $q.all(populatedItems);
        return populatingAllItems.then(function(items) {
            return items;
        }, function(err) {
          return err;
        });
      }
    },

test code:
it('Should not fail :)', function(done) {
csvParser.onCsvParse(csvResults).then(function(items) {
  //promise is resolved
  expect(items).not.toBeNull();
  done();
}, function() {
  //promise is rejeceted
  //expect(1).toEqual(1);
  done();
});
$rootScope.$apply();});

