import { getLanguageDictionary, getLanguagesDictionaries, registerLanguageDictionary } from 'handsontable/i18n/dictionariesManager'; import { register as registerPhraseFormatter } from 'handsontable/i18n/phraseFormatters'; import plPL from 'handsontable/i18n/languages/pl-PL'; import * as allLanguages from 'handsontable/i18n/languages'; import * as constants from 'handsontable/i18n/constants'; import Handsontable from 'handsontable'; import fs from 'fs'; describe('i18n', () => { beforeAll(() => { // Note: please keep in mind that this language will be registered for all unit tests! // It's stored globally for already loaded Handsontable library. Handsontable.languages.registerLanguageDictionary(plPL); }); const INSERT_ROW_ABOVE_DICTIONARY_KEY = 'ContextMenu:items.insertRowAbove'; const INSERT_ROW_ABOVE_IN_POLISH_LANGUAGE = 'Wstaw wiersz powyżej'; it('should extend Handsontable global by functions and constants for translation', () => { expect(Handsontable.languages.getLanguageDictionary).toBe(getLanguageDictionary); expect(Handsontable.languages.getLanguagesDictionaries).toBe(getLanguagesDictionaries); expect(Handsontable.languages.registerLanguageDictionary).toBe(registerLanguageDictionary); expect(Handsontable.languages.dictionaryKeys).toBe(constants); }); it('should get translated phrase by `getTranslatedPhrase` function', () => { expect(Handsontable.languages.getTranslatedPhrase(plPL.languageCode, INSERT_ROW_ABOVE_DICTIONARY_KEY)).toEqual(plPL[INSERT_ROW_ABOVE_DICTIONARY_KEY]); expect(Handsontable.languages.getTranslatedPhrase(plPL.languageCode, INSERT_ROW_ABOVE_DICTIONARY_KEY)).toEqual(INSERT_ROW_ABOVE_IN_POLISH_LANGUAGE); }); it('should get `null` when trying to translate phrase by passing language code of not registered language', () => { const notRegisteredLanguageCode = 'br-Za'; expect(Handsontable.languages.getTranslatedPhrase(notRegisteredLanguageCode, INSERT_ROW_ABOVE_DICTIONARY_KEY)).toEqual(null); }); it('should call formatters when `getTranslatedPhrase` function is called', () => { let formatterWasRun = false; registerPhraseFormatter('formatterChangingVariable', () => { formatterWasRun = true; }); expect(formatterWasRun).toEqual(false); Handsontable.languages.getTranslatedPhrase(plPL.languageCode, INSERT_ROW_ABOVE_DICTIONARY_KEY); expect(formatterWasRun).toEqual(true); }); describe('Collection of language packs', () => { // One of keys can be `__esModule` added by Babel. We separate it. const indexFileExportNames = Object.getOwnPropertyNames(allLanguages).filter(exportedKey => exportedKey !== '__esModule'); const dictionaries = indexFileExportNames.map(exportName => allLanguages[exportName]); const languageCodes = dictionaries.map(dictionary => dictionary.languageCode); const keysGroupedByDictionary = dictionaries.map(dictionary => Object.keys(dictionary)); const dictionariesKeys = [].concat([], ...keysGroupedByDictionary).filter(key => key !== 'languageCode'); const valuesGroupedByDictionary = dictionaries.map(dictionary => Object.values(dictionary)); const dictionariesValuesWithArrays = [].concat([], ...valuesGroupedByDictionary); const dictionariesValuesWithoutArrays = [].concat([], ...dictionariesValuesWithArrays); const filesInsideLanguageDictionary = fs.readdirSync('./src/i18n/languages'); const languagePacks = filesInsideLanguageDictionary.filter(name => name !== 'index.js'); describe('with every dictionary from `src/i18n/languages` folder', () => { // We take all dictionaries from export inside `src/i18n/languages/index.js` file. // We do not use dynamic import to take them from particular language files. // Workaround to stop next tests when below expectations fail. Without the conditions rest of tests is useless. // Jasmine problem described: https://github.com/jasmine/jasmine/issues/414 expect(indexFileExportNames.length).toBeGreaterThan(0); expect(languageCodes.length).toBeGreaterThan(0); expect(indexFileExportNames.length).toEqual(languagePacks.length); it('should not contain two or more dictionaries with the same language code', () => { expect(languageCodes).toEqual(Array.from(new Set(languageCodes).values())); }); describe('File `src/i18n/languages/index.js`', () => { it('should provide export names sorted alphabetically', () => { // Does ES6 introduce a well-defined order of enumeration for object properties? // https://stackoverflow.com/a/30919039 expect(indexFileExportNames.length).toBeGreaterThan(0); expect(indexFileExportNames).toEqual(indexFileExportNames.slice().sort()); }); it('should provide export names corresponding to dictionaries file names', () => { // two lowercase letters, two uppercase letters, for example: `esPY`. expect(indexFileExportNames).toBeListFulfillingCondition(exportName => languagePacks.map(languagePack => languagePack.replace('.js', '').replace('-', '')).includes(exportName)); }); }); describe('Particular dictionary', () => { it('should contain language code of proper format', () => { // two lowercase letters, hyphen, two uppercase letters, for example: `es-PY`. expect(languageCodes).toBeListFulfillingCondition(languageCode => /^([a-z]{2})-([A-Z]{2})$/.test(languageCode)); }); it('should have file name corresponding to its language code', () => { // two lowercase letters, hyphen, two uppercase letters and JavaScript file extension, for example: `es-PY.js`. expect(languagePacks).toBeListFulfillingCondition(languagePack => languageCodes.includes(languagePack.replace('.js', ''))); }); it('should contain only predefined keys (checking for typo)', () => { // If you would like to extend any dictionary by a custom key, define also corresponding, exported constant // for it inside the `src/i18n/constants.js` file. const predefinedDictionaryKeys = Object.values(constants); expect(dictionariesKeys).toBeListFulfillingCondition(dictionaryKey => predefinedDictionaryKeys.includes(dictionaryKey)); }); it('should contain values defined as strings or arrays of strings, without unnecessary whitespace characters', () => { expect(dictionariesValuesWithArrays).toBeListFulfillingCondition(value => typeof value === 'string' || Array.isArray(value)); expect(dictionariesValuesWithoutArrays).toBeListFulfillingCondition(value => typeof value === 'string'); expect(dictionariesValuesWithoutArrays).toBeListFulfillingCondition(value => value === value.trim()); expect(dictionariesValuesWithoutArrays).toBeListFulfillingCondition(value => / {2,}/.test(value) === false); }); }); }); }); });