$watch回调每次在浏览器中触发,但在测试期间只触发一次

$watch callback fires every time in browser but just once during the test

本文关键字:一次 测试 浏览器 回调 watch      更新时间:2023-09-26

我有一些代码应该存储有关过滤器更改的信息。在浏览器中,每次更改值时都会触发$watch回调。然而,当我试图在茉莉花/因果报应测试中复制这一点时,它在第一次改变后就着火了。

控制器

var tableFilter = this
  ;
var init0 = true
  ;
$scope.$watch( 'tableFilter.config.period', function () {
  console.log('watch');
  if ( !init0 ) {
    console.log('set dirty');
    tbFilterConfig.set( 'pristine', false );
    tbFilterConfig.setDirty( 'period' );
  }
  init0 = false;
} );

测试

describe( '$scope $watch', function () {
  it( 'should add period to dirty after second change', function () {
    $scope.$apply(console.log(1), controller.config.period = 'test');
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [] );
    $scope.$apply(console.log(2), controller.config.period = 'test2');
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [ 'period' ] );
  } );
});

控制台输出:

LOG: 1
LOG: 'watch'
LOG: 2
PhantomJS 1.9.8 (Windows 8 0.0.0) Controller: TableFilterCtrl $scope $watch should add period to dirty after second change FAILED
Expected [  ] to equal [ 'period' ].
        at C:/Projects/trackback-network-insight-ui/test/spec/controllers/table_filter.js:74

但在浏览器中:

setTimeout(function (  ) {
  $scope.$apply(console.log(1), tableFilter.config.period = 'test');
  $scope.$apply(console.log(2), tableFilter.config.period = 'test2');
}, 1000);

给我:

LOG: watch
LOG: 1
LOG: watch
LOG: set dirty
LOG: 2
LOG: watch
LOG: set dirty

这是意料之中的行为。

那我的考试怎么了?提前感谢!

编辑

完整控制器代码:

angular.module( 'insightApp' )
.controller( 'TableFilterCtrl', [ '$rootScope', 'tbConfig', '$scope', '$http', 'API_PATH', 'uiAlerts', 'tbFilterConfig',
  function ( $rootScope, tbConfig, $scope, $http, API_PATH, uiAlerts, tbFilterConfig ) {
    var tableFilter = this
      , newData = {}
      ;
    /* EXTRACT INITIAL DATA OR LOAD DATA IF TYPE OF DATA IS STRING */
    /* FETCH !!SELECT!! DATA FROM API */
    function fetchData( url, name ) {
      return $http.post( API_PATH + url, tbFilterConfig.get() )
        .success( function ( data ) {
          newData[ name ] = data;
        } )
        .error( function ( error ) {
          uiAlerts.set( {
            error: 'Ooops! We were unable to get filter data.'
          } );
          console.error( error );
        } );
    }
    tableFilter.filters = tbConfig.get( 'filters' );
    tableFilter.reports = tbConfig.get( 'reports' );
    tableFilter.config = tbFilterConfig.getRaw();
    /* RESET VALUES OF ALL DEPENDENT FILTERS */
    function resetDependent( name ) {
      var filters = tbConfig.get( 'filters' )
        ;
      filters.forEach( function ( filter ) {
        if ( filter.dependencies && filter.dependencies.indexOf( name ) > -1 ) {
          if ( tbFilterConfig.get( filter.key ) !== filter.defaultVal ) {
            tbFilterConfig.set( filter.key, filter.defaultVal );
            tbFilterConfig.setDirty( filter.key );
          }
        }
      } );
    }
    /* GET INITIAL DATA FOR SELECT OPTIONS */
    tableFilter.getData = function ( name, data, defaultVal ) {
      if ( !(name in newData) ) {
        newData[ name ] = [];
        if ( typeof data === 'string' ) {
          fetchData( data, name );
        } else if ( typeof data === 'object' ) {
          newData[ name ] = data;
        } else {
          console.error( 'Unexpected data type:', typeof data, data );
        }
        if ( !tbFilterConfig.get( name ) ) {
          tbFilterConfig.set( name, defaultVal );
        }
        var init = true;
        $scope.$watch( 'tableFilter.config.' + name, function () {
          if ( !init ) {
            resetDependent( name );
            tbFilterConfig.set( 'pristine', false );
            tbFilterConfig.setDirty( name );
          }
          init = false;
        } );
      }
      return newData[ name ];
    };
    /* WATCH FIXED PROPERTIES: PERIOD, DATE_FROM, DATE_TO */
    var init0 = true
      , init1 = true
      , init2 = true
      ;
    $scope.$watch( 'tableFilter.config.period', function () {
      if ( !init0 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'period' );
      }
      init0 = false;
    } );
    /* WATCH FIXED PROPERTIES DATE_FORM */
    $scope.$watch( 'tableFilter.config.date_from', function () {
      if ( !init1 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'date_from' );
      }
      init1 = false;
    } );
    /* WATCH FIXED PROPERTIES DATE_TO */
    $scope.$watch( 'tableFilter.config.date_to', function () {
      if ( !init2 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'date_to' );
      }
      init2 = false;
    } );
    /* UPDATE FILTER DATA */
    tableFilter.updateSelectData = function ( url, name, dependencies ) {
      if ( typeof url === 'string' ) {
        var touched = false;
        /* CHECK IF DEPENDENCIES CHANGED */
        for ( var i = 0; i < dependencies.length; i++ ) {
          if ( tableFilter.config.dirty.indexOf( dependencies[ i ] ) > -1 ) {
            touched = true;
            break;
          }
        }
        /* IF DEPENDENCIES CHANGED GET NEW DATA */
        if ( touched ) {
          return fetchData( url, name );
        }
      }
    };
  } ] );

和测试:

describe( 'Controller: TableFilterCtrl', function () {
// load the controller's module
beforeEach( function () {
  module( 'insightApp' );
} );
var controller
  , $scope
  , alerts
  , tbFilterConfigObj
  ;
// Initialize the controller and a mock scope
beforeEach( inject( function ( $controller, $rootScope, uiAlerts, tbFilterConfig ) {
  tbFilterConfigObj = tbFilterConfig;
  $scope = $rootScope.$new();
  alerts = uiAlerts;
  controller = $controller( 'TableFilterCtrl', {
    $scope: $scope
  } );
  $scope.$apply();
} ) );

describe( '$scope $watch', function () {
  it( 'should add period to dirty after second change', function () {
    $scope.$apply( console.log( 1 ), controller.config.period = 'test' );
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [] );
    $scope.$apply( console.log( 2 ), controller.config.period = 'test2' );
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [ 'period' ] );
  } );
} );

});

EDIT2

我对代码质量感到抱歉,但它还没有生产出来。

AngularJS在浏览器中运行时,它会运行digest周期来触发所有观察者及其回调。这就是Angular工作的原因,它是自动发生的。

这种机制在单元测试期间不起作用。运行测试时,需要通过调用$apply$digest手动触发摘要机制。

让我们来分析一下测试规范中发生的事情(它在控制台日志中显示得很好):

  1. $scope.$apply(console.log(1), controller.config.period = 'test');->将1写入控制台;改变CCD_ 7的值。

  2. $scope.$apply(console.log(2), controller.config.period = 'test2');->由于$apply首次触发tableFilter.config.period的观察者回调;将2写入控制台;更改config.period. 的值

在那之后,没有任何东西会第二次触发观察者的回调。

解决方案很简单:在每个expect语句之前添加对$scope.$digest()(或$scope.$apply())的调用。