Skip to content

Commit

Permalink
Added tests for SingleLanguageTranslationService and `translateSour…
Browse files Browse the repository at this point in the history
…ce` files.
  • Loading branch information
ma2ciek committed Nov 15, 2017
1 parent 94495e9 commit e7a11f7
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ module.exports = class MultipleLanguageTranslationService extends EventEmitter {
/**
* Translate file's source and replace `t()` call strings with short ids.
*
* @fires error
* @param {String} source
* @returns {String}
*/
translateSource( source ) {
return translateSource( source, translationKey => this._getId( translationKey ) );
const { output, errors } = translateSource( source, originalString => this._translateString( originalString ) );

for ( const error of errors ) {
this.emit( 'error', error );
}

return output;
}

/**
Expand All @@ -70,6 +78,7 @@ module.exports = class MultipleLanguageTranslationService extends EventEmitter {
/**
* Return an array of assets based on the stored dictionaries.
*
* @fires error
* @param {Object} param0
* @param {String} [param0.outputDirectory]
* @returns {Array.<Object>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,18 @@ module.exports = class SingleLanguageTranslationService extends EventEmitter {
/**
* Translate file's source and replace `t()` call strings with translated strings.
*
* @fires error
* @param {String} source
* @returns {String}
*/
translateSource( source ) {
return translateSource( source, originalString => this._translateString( originalString ) );
const { output, errors } = translateSource( source, originalString => this._translateString( originalString ) );

for ( const error of errors ) {
this.emit( 'error', error );
}

return output;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
const acorn = require( 'acorn' );
const walk = require( 'acorn/dist/walk' );
const escodegen = require( 'escodegen' );
const logger = require( '../logger' )();

/**
* Parses source, translates `t()` call arguments and returns modified output.
Expand All @@ -20,6 +19,7 @@ const logger = require( '../logger' )();
module.exports = function translateSource( source, translateString ) {
const comments = [];
const tokens = [];
const errors = [];

const ast = acorn.parse( source, {
sourceType: 'module',
Expand All @@ -37,7 +37,7 @@ module.exports = function translateSource( source, translateString ) {
}

if ( node.arguments[ 0 ].type !== 'Literal' ) {
logger.error( 'First t() call argument should be a string literal.' );
errors.push( 'First t() call argument should be a string literal.' );

return;
}
Expand All @@ -49,13 +49,13 @@ module.exports = function translateSource( source, translateString ) {

// Optimization for files without t() calls.
if ( !changesInCode ) {
return source;
return { output: source, errors };
}

escodegen.attachComments( ast, comments, tokens );
const output = escodegen.generate( ast, {
comment: true
} );

return output;
return { output, errors };
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,20 @@ const path = require( 'path' );
const proxyquire = require( 'proxyquire' );

describe( 'translations', () => {
describe( 'TranslationService', () => {
let TranslationService, stubs, files, fileContents, sandbox;
describe( 'SingleLanguageTranslationService', () => {
let SingleLanguageTranslationService, stubs, files, fileContents, sandbox;

beforeEach( () => {
sandbox = sinon.sandbox.create();

stubs = {
logger: {
info: sandbox.stub(),
warning: sandbox.stub(),
error: sandbox.stub()
},
fs: {
existsSync: path => files.includes( path ),
readFileSync: path => fileContents[ path ]
}
};

TranslationService = proxyquire( '../../lib/translations/translationservice', {
'../logger': () => stubs.logger,
SingleLanguageTranslationService = proxyquire( '../../lib/translations/singlelanguagetranslationservice', {
'fs': stubs.fs
} );
} );
Expand All @@ -40,37 +34,16 @@ describe( 'translations', () => {
} );

describe( 'constructor()', () => {
it( 'should be able to use custom function that returns path to the po file', () => {
const pathToTranslations = path.join( 'customPathToPackage', 'lang', 'translations', 'pl.po' );
it( 'should initialize `SingleLanguageTranslationService`', () => {
const translationService = new SingleLanguageTranslationService( 'pl' );

files = [ pathToTranslations ];

fileContents = {
[ pathToTranslations ]: [
'msgctxt "Label for the Save button."',
'msgid "Save"',
'msgstr "Zapisz"',
''
].join( '\n' )
};

const translationService = new TranslationService( 'pl', {
getPathToPoFile: ( pathToPackage, languageCode ) => {
return path.join( pathToPackage, 'lang', 'translations', `${ languageCode }.po` );
}
} );

translationService.loadPackage( 'customPathToPackage' );

expect( Array.from( translationService.dictionary ) ).to.deep.equal( [
[ 'Save', 'Zapisz' ]
] );
expect( translationService ).to.be.instanceof( SingleLanguageTranslationService );
} );
} );

describe( 'loadPackage()', () => {
it( 'should load po file from the package and load translations', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );
const pathToTranslations = path.join( 'pathToPackage', 'lang', 'translations', 'pl.po' );

files = [ pathToTranslations ];
Expand All @@ -86,24 +59,22 @@ describe( 'translations', () => {

translationService.loadPackage( 'pathToPackage' );

expect( Array.from( translationService.dictionary ) ).to.deep.equal( [
[ 'Save', 'Zapisz' ]
] );
expect( translationService._dictionary ).to.deep.equal( { 'Save': 'Zapisz' } );
} );

it( 'should do nothing if the po file does not exist', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );

files = [];
fileContents = {};

translationService.loadPackage( 'pathToPackage' );

expect( Array.from( translationService.dictionary ) ).to.deep.equal( [] );
expect( translationService._dictionary ).to.deep.equal( {} );
} );

it( 'should load po file from the package only once', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );
const loadPoFileSpy = sandbox.stub( translationService, '_loadPoFile' );

translationService.loadPackage( 'pathToPackage' );
Expand All @@ -115,47 +86,51 @@ describe( 'translations', () => {

describe( 'translateSource()', () => {
it( 'should translate t() calls in the code', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );
const source = 't( \'Cancel\' )';

translationService.dictionary.set( 'Cancel', 'Anuluj' );
translationService._dictionary.Cancel = 'Anuluj';

const result = translationService.translateSource( source );

expect( result ).to.equal( 't(\'Anuluj\');' );
} );

it( 'should return original source if there is no t() calls in the code', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );
const source = 'translate( \'Cancel\' )';

const result = translationService.translateSource( source );

expect( result ).to.equal( 'translate( \'Cancel\' )' );
} );

it( 'should lg the error and keep original string if the translation misses', () => {
const translationService = new TranslationService( 'pl' );
it( 'should emit an error and keep original string if the translation is missing', () => {
const translationService = new SingleLanguageTranslationService( 'pl' );
const source = 't( \'Cancel\' )';

const spy = sandbox.spy();
translationService.on( 'error', spy );

const result = translationService.translateSource( source );

expect( result ).to.equal( 't(\'Cancel\');' );
sinon.assert.calledOnce( stubs.logger.error );
sinon.assert.calledWithExactly( stubs.logger.error, 'Missing translation for: Cancel.' );
sinon.assert.calledOnce( spy );
sinon.assert.calledWithExactly( spy, 'Missing translation for Cancel for pl language.' );
} );

it( 'should throw an error when the t is called with the variable', () => {
const translationService = new TranslationService( 'pl' );
const translationService = new SingleLanguageTranslationService( 'pl' );
const source = 'const cancel = \'Cancel\';t( cancel );';

const spy = sandbox.spy();
translationService.on( 'error', spy );

const result = translationService.translateSource( source );

expect( result ).to.equal( 'const cancel = \'Cancel\';t( cancel );' );
sinon.assert.calledOnce( stubs.logger.error );
sinon.assert.calledWithExactly(
stubs.logger.error, 'First t() call argument should be a string literal.'
);
sinon.assert.calledOnce( spy );
sinon.assert.calledWithExactly( spy, 'First t() call argument should be a string literal.' );
} );
} );
} );
Expand Down
55 changes: 55 additions & 0 deletions packages/ckeditor5-dev-utils/tests/translations/translatesource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

'use strict';

const { expect } = require( 'chai' );
const sinon = require( 'sinon' );
const translateSource = require( '../../lib/translations/translatesource' );

describe( 'translations', () => {
describe( 'translateSource', () => {
let sandbox, translations;
const translateString = translationKey => translations[ translationKey ];

beforeEach( () => {
sandbox = sinon.sandbox.create();

translations = { 'Cancel': 'Anuluj' };
} );

afterEach( () => {
sandbox.restore();
} );

it( 'should translate t() calls in the code', () => {
const source = 't( \'Cancel\' )';

const { output, errors } = translateSource( source, translateString );

expect( output ).to.equal( 't(\'Anuluj\');' );
expect( errors.length ).to.equal( 0 );
} );

it( 'should return original source if there is no t() calls in the code', () => {
const source = 'translate( \'Cancel\' )';

const { output, errors } = translateSource( source, translateString );

expect( output ).to.equal( 'translate( \'Cancel\' )' );
expect( errors.length ).to.equal( 0 );
} );

it( 'should throw an error when the t is called with the variable', () => {
const source = 'const cancel = \'Cancel\';t( cancel );';

const { output, errors } = translateSource( source, translateString );

expect( output ).to.equal( 'const cancel = \'Cancel\';t( cancel );' );
// TODO: improve error message.
expect( errors ).to.deep.equal( [ 'First t() call argument should be a string literal.' ] );
} );
} );
} );

0 comments on commit e7a11f7

Please sign in to comment.