Home Angular 1.X Jasmine Testing: Use translater in a compiled template, directive or component
Reply: 2

Angular 1.X Jasmine Testing: Use translater in a compiled template, directive or component

L1ghtk3ira
1#
L1ghtk3ira Published in 2018-02-13 20:16:13Z

I have been reading into this scenario for the past several hours and it seems nobody knows how to do this.

Expectation: When testing a template or the outputted directive / component the translations such as <h1>{{home.title | translate}}</h1> should be translated to show the actual text <h1>Home Page</h1>.

Now after alot of digging I have been able to make it work by manually putting the translations I need in my test.

Example: Current test uses manual translation setup in test, The key here is $translateProvider.translations.

(function() {
'use strict';

describe('home component', function() {
  var rootscope, compile, directiveElement;
  beforeEach(module('Templates'));
  beforeEach(module('myApp'));

  beforeEach(module('tmh.dynamicLocale'), function () {
    tmhDynamicLocaleProvider.localeLocationPattern('base/angular/i18n/angular-locale_{{locale}}.js');
  });

  beforeEach(module('pascalprecht.translate', function ($translateProvider) { 
    $translateProvider.translations('en', {
      "home":{
         "title": "Home page"
      }
    });
  }));

  beforeEach(inject(function(_$rootScope_, _$compile_) {
    rootscope = _$rootScope_.$new();
    compile = _$compile_;
  }));

  function getCompiledElement(){
    var element = angular.element('<home-component></home-component');
    var compiledElement = compile(element)(rootscope);
    rootscope.$digest();
    return compiledElement;
  }

  describe('home', function () {
    it('should have template defined', function () {
     directiveElement = getCompiledElement();
     console.log('my component compiled', directiveElement);
    });
  });
 });
 })();

Output generated is correct:

Home page

Now this above compiles my component and shows the text as translated correctly instead of seeing the curly braces and the keys. Now in a realistic application it is not great to have to manually take the translations you need and put them in, as well as translations may change and you may forget to update your test.

I would like my tests to use the actual static json translation files

resources
  | locale-en_US.json

I have tried using the below code however with it being asyncronous it does not load by the times the tests are going. I need a way to either wait until the files are loaded or a different way to load the files into the $translateProvider.

$translateProvider.useStaticFilesLoader({
  prefix: 'app/resources/locale-', // path to translations files
  suffix: '.json'
});   

I also tried loading the language json files through karma.conf.js as shown below.

Files[
   ...
   { pattern: 'app/resources/angular-i18n/*.js', included: true, served: true },
   {pattern: 'app/resources/*.json', included: true, served: true},
 ]

I know there has to be a way this can work however I have not found a solution yet. Some say their solution works however I have tried using different plugins and still seems to not work.

I would like to find a solution for this as well as a good example to help other developers who I am sure are having trouble for full testing because of this.

I will continue to look on my end and any help is greatly appreciated. Thanks.

UPDATE: Custom-Loader

I am reading into creating a custom loader for the $translateProvider. I am not sure how to build it to handle the way I want so it can be used for testing properly but incase others are looking into this then this may be a spot to look at.

$provide.factory('customLoader', function ($q) {
    return function () {
      var deferred = $q.defer();
      deferred.resolve({});
      return deferred.promise;
    };
  });

  $translateProvider.useLoader('customLoader');
estus
2#
estus Reply to 2018-02-13 22:14:28Z

In order for unit tests to be most efficient for trouble solving, a unit should be isolated from other units. This way if a test becomes red, failed unit can be unambiguously determined. This is the reason why it's preferable to explicitly mock everything in unit tests and provide fixtures instead of relying on real data.

The use of third-party units breaks isolation, because their problems (bugs, package versions) cannot be distinguished from problems in units themselves. This makes debugging more costly.

The dependency on pascalprecht.translate can be eliminated in unit tests by providing mocked filter that will perform tested functionality in simplified, controllable way. To be tested against real data, Karma should be configured to support modules, e.g. karma-commonjs preprocessor, this way JSON data can be loaded directly and not through XHR request.

Lodash get is a good candidate to parse dot-separated paths, similarly to how translation service does this:

  var translationsEn = require('.../locale-en_US.json');
  ...
  beforeEach(angular.mock.module({ translateFilter: function (path) {
    return _.get(translationsEn, path);
  }));

This will mock translate filter (which is translateFilter service internally) with simple workaround. This will work as long as values doesn't use extended translation features like pluralization.

Alternatively, real translation service can be fixed similarly to how the guide suggests, but making use of CommonJS to load JSON files:

beforeEach(angular.mock.module(function ($translateProvider) {
  $translateProvider.translations('en', translationsEn);
}));

angular.mock.module should be used instead of module to avoid name collisions when CommonJS modules are in use.

L1ghtk3ira
3#
L1ghtk3ira Reply to 2018-02-13 22:19:29Z

Ok after digging deeper I think I came up with a solution that is not too hacky and fairly clean. First I installed and followed the instructions for this plugin Karma-fixture https://www.npmjs.com/package/karma-fixture

After setting up the fixture I call the global variable created by karma-fixture and use it in my test to grab the json file I want as well as load it cleanly into the $translateProvider.translations as shown below.

Note: Templates and fixture as well as files are important:

beforeEach(module('pascalprecht.translate', function ($translateProvider) {

   $translateProvider.translations('en', 
     fixture.load('app/resources/locale-en_US.json')
   );

   $translateProvider.preferredLanguage('en');
}));

Full working example of A Jasmine Unit test that compiles a component with translations below.

Karma.conf.js: File

module.exports = function(config) {
  'use strict';

   config.set({
     autoWatch: true,
     basePath: '../',

     frameworks: ['jasmine', 'fixture'],

     files: [
       // bower:js
       ... <-Bower files Here ->
       // endbower
       {pattern: 'app/resources/angular-i18n/*.js', included: true, served: true },
       {pattern: 'app/resources/*.json', included: true, served: true},
       'app/**/*.app.js',     // First load main module
       'app/**/*.module.js',  // Then Load all modules
       'app/**/*.tpl.html',   // Then Load all html pages
       'app/**/!(*.spec).js', // Then load all javascript files that are not tests
       'app/**/*.spec.js'     // Finally load all tests
     ],

     exclude: [
       'app/css/**/*.js',
       'app/js/**/*.js',
       'app/styles/vendor/**/*.js'
     ],

     port: 8080,

     browsers: ['PhantomJS'],

     plugins: [
       'karma-phantomjs-launcher',
       'karma-chrome-launcher', 
       'karma-jasmine',
       'karma-coverage',
       'karma-ng-html2js-preprocessor',
       'karma-json-fixtures-preprocessor',
       'karma-fixture'
     ],

     preprocessors: {
       'app/**/*.js': 'coverage',
       'app/**/*.html': 'ng-html2js',
       'app/resources/*.json'   : ['json_fixtures']
     },

     ngHtml2JsPreprocessor: {
       'moduleName': 'Templates',
       'stripPrefix': 'app/'
     },

     jsonFixturesPreprocessor: {variableName: '__json__'},

     reporters: ['progress', 'coverage'],
     singleRun: false,
     colors: true,
     logLevel: config.LOG_INFO,
   });
 };

Jasmine Unit Test: Bare Bones with component and translations utilized

(function() {
  'use strict';

  describe('Home component', function() {
    var rootscope, compile, componentElement;

    beforeEach(module('Templates'));
    beforeEach(module('app'));

    beforeEach(module('pascalprecht.translate', function ($translateProvider) {
      $translateProvider.translations('en', 
        fixture.load('app/resources/locale-en_US.json')
      );

      $translateProvider.preferredLanguage('en');
    }));

    beforeEach(inject(function(_$rootScope_, _$compile_) {
      rootscope = _$rootScope_.$new();
      compile = _$compile_;
    }));

    function getCompiledElement(){
      var element = angular.element('<home-component></home-component>');
      var compiledElement = compile(element)(rootscope);
      rootscope.$digest();
      return compiledElement;
    }

    describe('Home tests', function () {

      it('should have component defined', function () {
        componentElement = getCompiledElement();
        console.log('Compiled component with translations', componentElement);
        expect(componentElement).toBeDefined()
      });
    });
  });
})();

The above implementation will compile your component (can pass bindings etc) as well as utilize the translater and show your translations in your component in the console.

Instead of seeing: <h1>{{home.title | translate}}</h1> You will now see: <h1>Home Page</h1>

I hope this helps a lot of developers, if you need more clarification please add a comment and I will give more information as possible.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.302368 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO