Question Comment tester la directive sur la portée isolée dans AngularJS


Qu'est-ce qu'un bon moyen de tester le périmètre isolé dans AngularJS?

JSFiddle montrant le test unitaire

Extrait de directive

    scope: {name: '=myGreet'},
    link: function (scope, element, attrs) {
        //show the initial state
        greet(element, scope[attrs.myGreet]);

        //listen for changes in the model
        scope.$watch(attrs.myGreet, function (name) {
            greet(element, name);
        });
    }

Je veux m'assurer que la directive est à l'écoute des changements - ne pas travailler avec une portée isolée:

    it('should watch for changes in the model', function () {
        var elm;
        //arrange
        spyOn(scope, '$watch');
        //act
        elm = compile(validHTML)(scope);
        //assert
        expect(scope.$watch.callCount).toBe(1);
        expect(scope.$watch).toHaveBeenCalledWith('name', jasmine.any(Function));
    });

METTRE À JOUR: Je l'ai fait fonctionner en vérifiant si les observateurs attendus ont été ajoutés à la portée de l'enfant, mais il est très fragile et utilise probablement les accesseurs de manière non documentée (alias sujet à changement sans préavis!).

//this is super brittle, is there a better way!?
elm = compile(validHTML)(scope);
expect(elm.scope().$$watchers[0].exp).toBe('name');

MISE À JOUR 2: Comme je l'ai mentionné, c'est fragile! L'idée fonctionne toujours, mais dans les nouvelles versions d'AngularJS, l'accesseur a changé de scope() à isolateScope():

//this is STILL super brittle, is there a better way!?
elm = compile(validHTML)(scope);                       
expect(elm.isolateScope().$$watchers[0].exp).toBe('name');

81
2018-06-28 19:01


origine


Réponses:


Voir éléments angulaires api docs. Si tu utilises element.scope () vous obtenez la portée de l'élément que vous avez définie dans la propriété scope de votre directive. Si tu utilises element.isolateScope () vous obtenez toute la portée isolée. Par exemple, si votre directive ressemble à ceci:

scope : {
 myScopeThingy : '='
},
controller : function($scope){
 $scope.myIsolatedThingy = 'some value';
}

Ensuite, l'appel de element.scope () dans votre test va retourner

{ myScopeThingy : 'whatever value this is bound to' }

Mais si vous appelez element.isolateScope (), vous obtiendrez

{ 
  myScopeThingy : 'whatever value this is bound to', 
  myIsolatedThingy : 'some value'
}

Ceci est vrai pour les angles 1.2.2 ou 1.2.3, pas sûr exactement. Dans les versions précédentes, vous n'aviez que element.scope ().


100
2017-12-01 13:32



Tu peux faire var isolateScope = myDirectiveElement.scope() pour obtenir la portée de l'isolat.

Vous n'avez pas vraiment besoin de tester que $ watch a été appelé bien que cela teste plus angularjs que de tester votre application. Mais je suppose que c'est juste un exemple pour la question.


11
2018-06-28 22:16



déplacer la logique vers un contrôleur distinct, à savoir:

//will get your isolate scope
function MyCtrl($scope)
{
  //non-DOM manipulating ctrl logic here
}
app.controller(MyCtrl);

function MyDirective()
{
  return {
    scope     : {},
    controller: MyCtrl,
    link      : function (scope, element, attrs)
    {
      //moved non-DOM manipulating logic to ctrl
    }
  }
}
app.directive('myDirective', MyDirective);

et testez ce dernier comme vous le feriez avec n'importe quel contrôleur - en passant directement l'objet scope (voir Contrôleurs  section ici à titre d'exemple).

si vous devez déclencher $ watch dans votre test, faites:

describe('MyCtrl test', function ()
{
  var $rootScope, $controller, $scope;

  beforeEach(function ()
  {
    inject(function (_$rootScope_, _$controller_)
    {
      // The injector unwraps the underscores (_) from around the parameter names when matching
      $rootScope = _$rootScope_;
      $controller = _$controller_;
    });

    $scope = $rootScope.$new({});
    $scope.foo = {x: 1}; //initial scope state as desired
    $controller(MyCtrl, {$scope: $scope}); //or by name as 'MyCtrl'
  });

  it('test scope property altered on $digest', function ()
  {
    $scope.$digest(); //trigger $watch
    expect($scope.foo.x).toEqual(1); //or whatever
  });
});

1
2018-01-29 23:27



Je ne suis pas sûr que ce soit possible avec une portée isolée (bien que j'espère que quelqu'un me prouve le contraire). La portée d'isolat créée dans la directive est, bien, isolée, de sorte que la méthode $ watch de la directive est différente de la portée que vous espionnez dans le test unitaire. Si vous changez d'étendue: {} en scope: true, la portée de la directive héritera de manière prototypique et vos tests devraient être réussis.

Je suppose que ce n’est pas la solution la plus idéale, car parfois (la plupart du temps), isoler la portée est une bonne chose.


0
2018-06-28 20:57