diff --git a/imports/i18n/i18n.test.js b/imports/i18n/i18n.test.js new file mode 100644 index 000000000..c51d4a525 --- /dev/null +++ b/imports/i18n/i18n.test.js @@ -0,0 +1,130 @@ +import { Tracker } from 'meteor/tracker'; +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import { TAPi18n } from './tap'; + +chai.use(require('sinon-chai')); +chai.use(require('chai-as-promised')); + +describe('TAPi18n', () => { + + beforeEach(() => { + sinon.stub(console, 'log'); + }); + + afterEach(() => { + sinon.restore(); + }); + + if (Meteor.isClient) { + it('extra languages are not loaded initially', () => { + // Using function here to prevent Meteor from including the file + // during building time + function path(language) { + return `./data/${language}.i18n.json`; + } + expect(() => require(path('de'))).to.throw('Cannot find module'); + }); + } + + describe('.init', () => { + + it('has default language translation loaded', async () => { + sinon.spy(TAPi18n, 'loadLanguage'); + await TAPi18n.init(); + expect(TAPi18n.loadLanguage).to.be.calledWith('en'); + expect(TAPi18n.getLanguage()).to.be.equal('en'); + expect(TAPi18n.__('accept')).to.be.equal('Accept'); + }); + + }); + + describe('.getSupportedLanguages', () => { + + it('returns an array of objects with expected structure', () => { + const languages = TAPi18n.getSupportedLanguages(); + expect(languages).to.be.an('array'); + for (const language of languages) { + expect(language).to.have.keys('name', 'code', 'isoCode'); + } + }); + + }); + + describe('.getLanguage', () => { + + it('is reactive', async () => { + const tracked = []; + Tracker.autorun(() => { + tracked.push(TAPi18n.getLanguage()); + }); + expect(tracked).to.have.members(['en']); + await TAPi18n.setLanguage('de'); + Tracker.flush(); + expect(tracked).to.have.members(['en', 'de']); + }); + + }); + + describe('.loadLanguage', () => { + + beforeEach(() => { + sinon.stub(TAPi18n.i18n, 'addResourceBundle'); + }); + + it('actually loads the language data', async () => { + await TAPi18n.loadLanguage('fr'); + expect(TAPi18n.i18n.addResourceBundle).to.be.calledOnceWith('fr'); + expect(TAPi18n.i18n.addResourceBundle.firstCall.args[2]).to.have.property('accept'); + }); + + it('does nothing if language is missing', async () => { + await expect(TAPi18n.loadLanguage('miss')).to.be.fulfilled; + expect(TAPi18n.i18n.addResourceBundle).to.not.be.called; + }); + + }); + + describe('.__', () => { + + beforeEach(async () => { + await TAPi18n.init(); + TAPi18n.i18n.addResourceBundle('fr', 'translation', { + 'simple': 'Hello', + 'positional': 'Hello, %s! How is your %s?', + 'named': 'Hello, __whom__! How is your __what__?', + }); + TAPi18n.i18n.changeLanguage('fr'); + }); + + it('works with simple keys', () => { + const result = TAPi18n.__('simple'); + expect(result).to.be.equal('Hello'); + }); + + it('works with simple keys and custom language', async () => { + TAPi18n.i18n.addResourceBundle('de', 'translation', { + 'simple': 'Hola', + }); + const result = TAPi18n.__('simple', null, 'de'); + expect(result).to.be.equal('Hola'); + }); + + it('works with positional parameters', () => { + const result = TAPi18n.__('positional', { + sprintf: ['Josh', 'life'] + }); + expect(result).to.be.equal('Hello, Josh! How is your life?'); + }); + + it('works with named parameters', () => { + const result = TAPi18n.__('named', { + whom: 'Annie', + what: 'job' + }); + expect(result).to.be.equal('Hello, Annie! How is your job?'); + }); + + }); + +}); diff --git a/tests/main.js b/tests/main.js index 648be68c2..1f83ffb5b 100644 --- a/tests/main.js +++ b/tests/main.js @@ -17,6 +17,8 @@ // they are. However, if you want to restict tests to server-only or client-only // you need to wrap them inside a new describe-block +import '/imports/i18n/i18n.test.js'; + if (Meteor.isServer) { describe('server', function() { import '../server/lib/tests/utils.tests';