webteam - 14/06/2016

CONVENTIONS DE CODAGE 

CE QUI VA PAS

1 - Routes : basées sur des pages/views (w-href, "_params")

2 - Controlleurs : la "scope soup" infâme

3 - La logique dans le template html

4 - L'écriture du code

5 - Le manque de tests unitaires

6 - Commentaires de commit : "no message", "blabla", "prout"

 

 

 

Solution : Routes

1 - Routes : basées sur des pages/views (w-href, "_params")

- routes basées sur du "state" (faire ce qu'on fait dans .run, éviter $rootScope, $wStore..)

// Gestion des "resolve"
getSettings.$injector = ['$wModApi'];
function getSettings($wModApi) {
    return $wModApi.settings.get();
}

// Gestion des routes
stateHelperProvider.state({
    name: 'root',
    template: '<ui-view/>',
    abstract: true,
    resolve: {
	settings: getSettings
    },
    children: [{
	name: 'EnterPhoneNumber',
	url: '/EnterPhoneNumber',
	templateUrl: 'app/views/EnterPhoneNumber/EnterPhoneNumber.html',
	controller: 'EnterPhoneNumberCtrl',
	controllerAs: 'ctrl'
    }]
});

$urlRouterProvider
    .when('EnterPhoneNumber', 'EnterPhoneNumber')
    .otherwise('EnterPhoneNumber');

Solution : SCOPE SOUP

2 - Controlleurs : la "scope soup" infâme

// Controlleur
(function () {
    'use strict';

    angular
        .module('app')
        .controller('CounterCtrl', CounterCtrl);

    CounterCtrl.$inject = ['$location', '$routeParams', 'common', 'dataservice'];

    function CounterCtrl($location, $routeParams, common, dataservice) {
        var ctrl = this;
        function increment() {
          ctrl.count++;
        }
        function decrement() {
          ctrl.count--;
        }
        ctrl.increment = increment;
        ctrl.decrement = decrement;
    }
}());

Solution : SCOPE SOUP

2 - Controlleurs : la "scope soup" infâme

// Composant (remplaçant de directive qui prend un controlleur, oublier la fonction "link")
(function () {
    'use strict';
    
    angular
        .module('app')
        .component('counter', counter);
    
    var counter = {
        bindings: {
            count: '=', // two-ways databinding
            countOneWay: '<' // one way databinding (plus besoin des :: dans le template html)
        },
        controller: CounterCtrl
    };

}());

Solution : CONVENTION

4 - L'écriture du code

- Tabs 4

- ESLint

- Attention aux boucles for in, for. = préférer prog fonctionnelle : utiliser Array.prototype.filter(), foreach, etc

var obj = {
    firm: "Watchever",
    team: "webteam"
};

for (var prop in obj) {
    if( obj.hasOwnProperty(prop) ) {
        console.log("obj." + prop + " = " + obj[prop]);
    }
}

// obj.firm = Watchever
// obj.team = webteam

Solution : CONVENTION

4 - L'écriture du code

- Attention diff notation JSON != object :

// object
var mon_objet = {
    firm: "Watchever",
    team: webteam
};


// JSON notation
{
    "firm": "Watchever",
    "team": "webteam"
}

Solution : CLEANING

parce que c'est bien

- supprimer variables de $scope inutilisées ou bindées inutilement (search dans le template)

-idem pour les services injectés au ctrl

 

pour faire simple : fonction init() et variables de $scope déclarées et commentées au début ou à la fin du fichier

=> avec angular 1.5 $onInit() et bindings{}

 

-transférer les gros bouts de code js dans des services, par unité de sens

Solution : PROVIDER

injectable en phase de configuration

navigationProvider.jsonBreakpointsFilename = '/app/css/breakpoints.json';

service > factory > provider

retourne un objet js, à grouper par thème

provider : doit avoir un méthode $get attachée à son this qui retourne l'objet

(la factory retourne l'objet directement, le service attache tout à this cf exemples)

injection des dépendances au niveau du $get !

(function () {
    'use strict';
    
    angular
        .module('app')
        .component('navigation', navigationProvider);
    
    function navigationProvider() {
        'use strict';
        var that = this;
	/* On déclare des données attachée sur le provider. PAS sur l objet que le provider va créer, attention. Sur le provider lui-même.*/
	that.jsonBreakpointsFilename = null;
        that.breakpoints = null;
        // fonction anonyme qui retourne un objet (principe du provider)
	that.$get = ['$http', 
	    function($http) {
		var navigation = {
                    setBreakpoints: function setBreakpoints() {
			if (that.jsonBreakpointsFile !== null) {
			    $http.get(that.jsonBreakpointsFilename).success(function(data) {
				that.breakpoints = data;
			    });
			} else {
			    console.log("navigation: breakpoints.json file not initialised !");
			}
		    },
		    getBreakpoints: function getBreakpoints(name) {
			if (typeof that.breakpoints[name] !== "undefined") {
			    return parseInt(that.breakpoints[name].screen);
			}
                    }
                };			
    		return navigation;
    	    }];
    }
}());
(function () {
    'use strict';

    angular
        .module('app')
        .factory('profile', profile);

    function profile() {
        return {
            name: "Anonymous",
            id: null,
            login: function login() {
                // faire un truc pour se loger
            },
            logout: function logout() {
                // faire un truc pour se deco
            }
        };
    }
}());
(function () {
    'use strict';
    angular
        .module('app')
        .service('profile', profile);

    function profile() {
        var that = this;
        that.name = "Anonymous";
        that.id = null;
        that.login = function login() {
            // faire un truc pour se loger
        }
        that.logout = function logout() {
            // faire un truc pour se deco
        }
    }
}());

Solution : LOADING

ne plus utiliser les currentStep mais des promises

- $rootScope.loading qui conditionne l'affichage

 

puis dans les ctrl:

var getTracks = function(contentId) {
    var deferred = $q.defer();
    				
    $wModApi.users_playlists.me.contents.get(contentId, fields, range, expand).then(function (data) {
        [...]

        deferred.resolve();
    });

    return deferred.promise;
};


var init = function(){
    getTracks(contentId).then(function() {
        [...]

        $rootScope.loading = false;
    };
};

dans le cas de promises multiples, chaînage

var getTracks = function(contentId) {
    var deferred = $q.defer();
    				
    $wModApi.users_playlists.me.contents.get(contentId, fields, range, expand).then(function (data) {
        [...]

        deferred.resolve();
    });

    return deferred.promise;
};

var getRelatedTitles = function (contentId) {
    var deferred = $q.defer();
				
    $wModApi.contents.similars.get(contentId, range, expand, fields).then(function (data) {
        [...]
	deferred.resolve();
    });
	
    return deferred.promise;
}


var init = function(){
    var p1 = getTracks(contentId);
    var p2 = getRelatedTitles(contentId);

    $q.all([p1.then(...), p2.then(...)].then(function() {
        $rootScope.loading = false;
    });
};

Solution : snippets

on centralise tout dans w_mod_seed (todo gulpfile unifié en passant)

var deferred = $q.defer();

deferred.notify();
deferred.resolve();
deferred.reject();

return deferred.promise;
var promise = asyncFn();
promise.then(function() {
  alert('Success');
}, function(reason) {
  alert('Failed: ' + reason);
}, function(update) {
  alert('Got notification: ' + update);
});

deferred

PROMISE

$scope.$emit('event', element);
$scope.$broadcast('event', element);
$scope.$on('event', function($event) {

});

emit

broadcast

ON

>

>

$timeout(function(){
    //delayed
},1000);

timeout

$translate('PP_label_ItemNbr').then(function(data) {
	$scope.label = data;
});

translate

$translate('PP_label_ItemNbr',{ v0: 10 }).then(function(data) {
	$scope.label = data;
});

translate keys

{{'PP_label_ItemNbr' | translate}}
<h3 translate="{{ 'PP_label_ItemNbr' }}" translate-values="{v0 : content.title}"></h3>

! ajouter la clé dans hiddentranslate.html

ngDialog.open({
	showClose: true,
	preCloseCallback: function () {},
	template: 'app/views/dialog/myDialog.html',
	controller: ['$scope', function ($dialogScope) {
		$dialogScope.type = 'remove';
		$dialogScope.delete = function () {
			
		};
	}]
});

Dialog

Solution : w_mod_seed

  • snippets (+export IDE brackets, eclipse)
  • fichiers clean : provider, directive, controller, app (routes..)
  • gulp unifié
  • filters utiles (characters, words..)
  • services généraux (debounce..)
  • inté : css, img ?

Solution : BOWER COMPONENT

Solution1 : bower link

install

- bower link dans le projet w_mod_tile

- bower link w-mod-tile dans le projet principal wmusic (bower unlink w-mod-tile)

maj

pull du composant

 

Solution2: bower Update

1 - faire ses modifs dans le composant (gulp watch qui tourne) ->commit->gulp publish (v2.2.7)

2- faire évoluer la version dans le bower.json du projet principal ->commit

"w-mod-tile": "git@git.std1.prod.adm:w_mod_tile.git#v2.2.7"

3-avertir les copains sur slack

maj

pull projet principal, bower update

 

HOW TO DEV

Solution : COMMITS COMMENTS

6 - Commentaires de commit : "no message", "blabla", "prout"

  • fix(Mantis): copié/collé du titre du mantis
  • feat(Fonctionnalité): description de la fonctionnalité
  • mettre « BREAKING CHANGES » dans la description si le commit casse l’existant
  • perf(Titre): description de la perf
  • test(Titre): description du test mis en place

ATTENTION : pas d'espaces entre ) et :

Solution : UNIT TESTING

5 - Le manque de tests unitaires

On réalise les tests unitaire avec les outils suivants :

 

- Karma : L'outil CLI qui permet de tester des applications sur navigateurs

- Jasmine: Un framework javascript permettant de structurer les tests  avec en plus des fonctions prêtes à l'emploi

- NgMock: Il permet d' injecter et de simuler les services d'AngularJS d'où l'intérêt d' éviter les variables globales (dans l'objet window) pour pouvoir les mocker facilement en faisant des services

 

Chaque test se trouve dans le dossier du code à tester

Le controller

Tester un controller

(function () {
    'use strict';

     angular
        .module('myApp.view3')
        .controller('View3Ctrl', View3Ctrl);

    View3Ctrl.$inject = ['$scope', 'view3Service'];

    function View3Ctrl($scope, view3Service) {
	var vm = this;
	vm.name = view3Service.getViewName();
    }
}());

Le contenu est chargé via un service que nous allons devoir mocker

Le TEST

Tester un controller

'use strict';

describe('myApp.view3 module', function () {
    var $controller, view3ServiceMock, $scope, controller;

    beforeEach(function() {
	view3ServiceMock = jasmine.createSpyObj('view3Service', ['getViewName']);
	module('myApp.view3');

	inject(function($rootScope, $controller) {
	    $scope = $rootScope.$new();
	    view3ServiceMock.getViewName.and.returnValue('My name');
    	    controller = $controller('View3Ctrl', {
    	        $scope: $scope,
    	        view3Service: view3ServiceMock
    	    });
        });

    });
    it('should return the name', function (){	
        $scope.name = view3ServiceMock.getViewName();
	expect(view3ServiceMock.getViewName).toHaveBeenCalled();
	expect($scope.name).toEqual('My name'); // expect(controller.name).toEqual('My name');
    });
});

Le SERVICE

Tester un service

(function () {
    'use strict';

     angular
        .module('myApp.view3')
        .service('view3Service', view3Service);

    view3Service.$inject = ['$http', '$q'];

    function View3Ctrl($http, $q) {
	return {
            url: 'path_to_url',
            getViewName: function(){
              q = $q.defer()
              $http.jsonp(url)
              .then(function(result){
                q.resolve(result.data)
              })
              return q.promise;
            }
        };
}());

Le Test de la fonction

Tester un service

describe('view3Service', function(){

  var view3Service;

  beforeEach(function(){
    angular.mock.module('myApp.view3')
    angular.mock.inject(function(_view3Service_){
      view3Service= _view3Service_
    })
  })

    it("Should have a getViewName", function(){
     expect(view3Service.getViewName).to.be.a('function')
    })

});

Le Test plus complet

Tester un service

describe('view3Service', function(){
  var view3Service, $http, url = "http://url/api.fr";
  beforeEach(function(){
    angular.mock.module('myApp.view3')
    angular.mock.inject(function(_view3Service_, $httpBackend){
      view3Service= _view3Service_
      $http = $httpBackend
    })
  })
  afterEach(function(){
    $http.verifyNoOutstandingExpectation();
    $http.verifyNoOutstandingRequest();
  })
  it("Should have a getViewName method", function(){
    expect(view3Service.getViewName).to.be.a('function')
  })
  it("Should call JSONP", function(){
    $http.expectJSONP(view3Service.url).respond(false)
    view3Service.getViewName(url)
    $http.flush()
  })
  it("Should return count", function(){
    count = 0;
    $http.expectJSONP(view3Service.url).respond({count: 2})
    view3Service.getViewName(url).then(function(c){
      count = c
    })
    $http.flush()
    expect(count).to.be.equal(2)
  })
})

La directive

Tester une directive

(function () {
    'use strict';
     angular.module('myApp.directives').directive('alerts', alerts);

    function alerts() {
        return {
            restrict: 'E',
            template: '<div><div class="alert" ng-repeat="message in messages">{{ message.msg }}</div></div>',
            scope: {
              messages: '=msgs'
            },
            link: function(scope, element, attrs){
              scope.demo = 1
            }
      };
}());

LE TEST

Tester une directive

describe.only('Alert directive', function(){
  var scope? element;

  beforeEach(function(){
    angular.mock.module('myApp.directives')
    angular.mock.inject(function($compile, $rootScope){
      scope = $rootScope.$new()
      element = $($compile('<alerts msgs="aa"></alerts>')(scope))
      $('body').append(element)
      scope.$digest()
    })
  });
  afterEach(function(){
    element.remove()
  })
  it('Should display alerts', function(){
    scope.aa = [{type: 'success', msg: 'Bravo'}, {type: 'success', msg: 'Bravo'}]
    scope.$digest()
    expect($('.alert', element).length).to.be.equal(2)
    expect($('.alert:first', element).text()).to.be.equal('Bravo')
  })
  it('Should have dmeo in the scope', function(){
    expect(element.isolateScope().demo).to.be.equal(1)
  })
  it('should be visible', function(){
    scope.aa = [{type: 'success', msg: 'Bravo'}]
    scope.$digest()
    expect($('.alert').is(':visible')).to.be.true
  })
});

LE PROVIDER

Tester un provider

(function () {
    'use strict';

    sample.$inject = ['config', 'anotherProvider'];
    angular.module('myApp.providers').provider('sample', sample);

    function sample(config, anotherProvider) {
        this.configureOptions = function(options){
          if(options.allow){
            anotherProvider.register(appConstants.ALLOW);
          } else {
            anotherProvider.register(appConstants.DENY);
          }
        };

        this.$get = function(){};
    }
}());

LE TEST

Tester un provider

beforeEach(module("myApp.providers"));

beforeEach(function(){
  module(function(anotherProvider, config, sampleProvider){
    _anotherProvider_=anotherProvider;
    _config_=config;
    _sampleProvider_=sampleProvider;
  });
});
beforeEach(inject());

it('should call register with allow', function(){
  _sampleProvider_.configureOptions({allow:true});
  expect(_anotherProvider_.register).toHaveBeenCalled();
  expect(_anotherProvider_.register).toHaveBeenCalledWith(_config_.ALLOW);
});

LIENS

gulpjs.com

bower.io

http://tracking.std1.prod.adm/projects/web-webapp/wiki

Conventions de codage

By Cyril Moreau

Conventions de codage

webteam Watchever

  • 660