diff --git a/src/utils/arrayDifference.ts b/src/utils/arrayDifference.ts new file mode 100644 index 000000000000..d011f8d5067b --- /dev/null +++ b/src/utils/arrayDifference.ts @@ -0,0 +1,9 @@ +/** + * This function is an equivalent of _.difference, it takes two arrays and returns the difference between them. + * It returns an array of items that are in the first array but not in the second array. + */ +function arrayDifference(array1: TItem[], array2: TItem[]): TItem[] { + return [array1, array2].reduce((a, b) => a.filter((c) => !b.includes(c))); +} + +export default arrayDifference; diff --git a/tests/unit/LocalizeTests.js b/tests/unit/LocalizeTests.ts similarity index 100% rename from tests/unit/LocalizeTests.js rename to tests/unit/LocalizeTests.ts diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.ts similarity index 63% rename from tests/unit/TranslateTest.js rename to tests/unit/TranslateTest.ts index d23fa52fc798..0be29a29cb12 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.ts @@ -1,12 +1,15 @@ -import {AnnotationError} from '@actions/core'; -import _ from 'underscore'; -import CONFIG from '../../src/CONFIG'; -import CONST from '../../src/CONST'; -import * as translations from '../../src/languages/translations'; -import * as Localize from '../../src/libs/Localize'; - -const originalTranslations = _.clone(translations); -translations.default = { +/* eslint-disable @typescript-eslint/naming-convention */ +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; +import * as translations from '@src/languages/translations'; +import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import * as Localize from '@src/libs/Localize'; +import asMutable from '@src/types/utils/asMutable'; +import arrayDifference from '@src/utils/arrayDifference'; + +const originalTranslations = {...translations}; + +asMutable(translations).default = { [CONST.LOCALES.EN]: translations.flattenObject({ testKey1: 'English', testKey2: 'Test Word 2', @@ -24,80 +27,86 @@ translations.default = { describe('translate', () => { it('Test present key in full locale', () => { - expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey1')).toBe('Spanish ES'); + expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey1' as TranslationPaths)).toBe('Spanish ES'); }); it('Test when key is not found in full locale, but present in language', () => { - expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey2')).toBe('Spanish Word 2'); - expect(Localize.translate(CONST.LOCALES.ES, 'testKey2')).toBe('Spanish Word 2'); + expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey2' as TranslationPaths)).toBe('Spanish Word 2'); + expect(Localize.translate(CONST.LOCALES.ES, 'testKey2' as TranslationPaths)).toBe('Spanish Word 2'); }); it('Test when key is not found in full locale and language, but present in default', () => { - expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey3')).toBe('Test Word 3'); + expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey3' as TranslationPaths)).toBe('Test Word 3'); }); test('Test when key is not found in default', () => { - expect(() => Localize.translate(CONST.LOCALES.ES_ES, 'testKey4')).toThrow(Error); + expect(() => Localize.translate(CONST.LOCALES.ES_ES, 'testKey4' as TranslationPaths)).toThrow(Error); }); test('Test when key is not found in default (Production Mode)', () => { const ORIGINAL_IS_IN_PRODUCTION = CONFIG.IS_IN_PRODUCTION; - CONFIG.IS_IN_PRODUCTION = true; - expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey4')).toBe('testKey4'); - CONFIG.IS_IN_PRODUCTION = ORIGINAL_IS_IN_PRODUCTION; + asMutable(CONFIG).IS_IN_PRODUCTION = true; + expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey4' as TranslationPaths)).toBe('testKey4'); + asMutable(CONFIG).IS_IN_PRODUCTION = ORIGINAL_IS_IN_PRODUCTION; }); it('Test when translation value is a function', () => { const expectedValue = 'With variable Test Variable'; const testVariable = 'Test Variable'; - expect(Localize.translate(CONST.LOCALES.EN, 'testKeyGroup.testFunction', {testVariable})).toBe(expectedValue); + // @ts-expect-error - TranslationPaths doesn't include testKeyGroup.testFunction as a valid key + expect(Localize.translate(CONST.LOCALES.EN, 'testKeyGroup.testFunction' as TranslationPaths, {testVariable})).toBe(expectedValue); }); }); describe('Translation Keys', () => { - function traverseKeyPath(source, path, keyPaths) { - const pathArray = keyPaths || []; + function traverseKeyPath(source: TranslationFlatObject, path?: string, keyPaths?: string[]): string[] { + const pathArray = keyPaths ?? []; const keyPath = path ? `${path}.` : ''; - _.each(_.keys(source), (key) => { - if (_.isObject(source[key]) && !_.isFunction(source[key])) { + (Object.keys(source) as Array).forEach((key) => { + if (typeof source[key] === 'object' && typeof source[key] !== 'function') { + // @ts-expect-error - We are modifying the translations object for testing purposes traverseKeyPath(source[key], keyPath + key, pathArray); } else { pathArray.push(keyPath + key); } }); + return pathArray; } + const excludeLanguages = [CONST.LOCALES.EN, CONST.LOCALES.ES_ES]; - const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const languages = Object.keys(originalTranslations.default).filter((ln) => !excludeLanguages.some((excludeLanguage) => excludeLanguage === ln)); const mainLanguage = originalTranslations.default.en; const mainLanguageKeys = traverseKeyPath(mainLanguage); - _.each(languages, (ln) => { - const languageKeys = traverseKeyPath(originalTranslations.default[ln]); + languages.forEach((ln) => { + const languageKeys = traverseKeyPath(originalTranslations.default[ln as keyof typeof originalTranslations.default]); it(`Does ${ln} locale have all the keys`, () => { - const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); + const hasAllKeys = arrayDifference(mainLanguageKeys, languageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); - AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + Error(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); } expect(hasAllKeys).toEqual([]); }); it(`Does ${ln} locale have unused keys`, () => { - const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); + const hasAllKeys = arrayDifference(languageKeys, mainLanguageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); - AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + Error(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); } expect(hasAllKeys).toEqual([]); }); }); }); +type ReportContentArgs = {content: string}; + describe('flattenObject', () => { it('It should work correctly', () => { - const func = ({content}) => `This is the content: ${content}`; + const func = ({content}: ReportContentArgs) => `This is the content: ${content}`; const simpleObject = { common: { yes: 'Yes', diff --git a/tests/unit/createOrUpdateStagingDeployTest.js b/tests/unit/createOrUpdateStagingDeployTest.ts similarity index 97% rename from tests/unit/createOrUpdateStagingDeployTest.js rename to tests/unit/createOrUpdateStagingDeployTest.ts index 75600f0d4fc8..38ba4942a785 100644 --- a/tests/unit/createOrUpdateStagingDeployTest.js +++ b/tests/unit/createOrUpdateStagingDeployTest.ts @@ -1,7 +1,8 @@ /** * @jest-environment node */ -import * as core from '@actions/core'; + +/* eslint-disable @typescript-eslint/naming-convention */ import * as fns from 'date-fns'; import {vol} from 'memfs'; import path from 'path'; @@ -19,7 +20,9 @@ const mockGetPullRequestsMergedBetween = jest.fn(); beforeAll(() => { // Mock core module - core.getInput = mockGetInput; + jest.mock('@actions/core', () => ({ + getInput: mockGetInput, + })); // Mock octokit module const moctokit = { @@ -49,8 +52,8 @@ beforeAll(() => { list: jest.fn().mockResolvedValue([]), }, }, - paginate: jest.fn().mockImplementation((objectMethod) => objectMethod().then(({data}) => data)), - }; + paginate: jest.fn().mockImplementation((objectMethod: () => Promise<{data: unknown}>) => objectMethod().then(({data}) => data)), + } as typeof GithubUtils.octokit; GithubUtils.internalOctokit = moctokit; // Mock GitUtils @@ -295,7 +298,7 @@ describe('createOrUpdateStagingDeployCash', () => { owner: CONST.GITHUB_OWNER, repo: CONST.APP_REPO, issue_number: openStagingDeployCashBefore.number, - // eslint-disable-next-line max-len + // eslint-disable-next-line max-len, @typescript-eslint/naming-convention html_url: `https://github.com/Expensify/App/issues/${openStagingDeployCashBefore.number}`, // eslint-disable-next-line max-len body: @@ -371,7 +374,7 @@ describe('createOrUpdateStagingDeployCash', () => { owner: CONST.GITHUB_OWNER, repo: CONST.APP_REPO, issue_number: openStagingDeployCashBefore.number, - // eslint-disable-next-line max-len + // eslint-disable-next-line max-len, @typescript-eslint/naming-convention html_url: `https://github.com/Expensify/App/issues/${openStagingDeployCashBefore.number}`, // eslint-disable-next-line max-len body: diff --git a/tests/unit/enhanceParametersTest.js b/tests/unit/enhanceParametersTest.ts similarity index 96% rename from tests/unit/enhanceParametersTest.js rename to tests/unit/enhanceParametersTest.ts index 6829732f1633..bf2756767401 100644 --- a/tests/unit/enhanceParametersTest.js +++ b/tests/unit/enhanceParametersTest.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; import CONFIG from '../../src/CONFIG'; import enhanceParameters from '../../src/libs/Network/enhanceParameters'; diff --git a/tests/unit/nativeVersionUpdaterTest.js b/tests/unit/nativeVersionUpdaterTest.ts similarity index 100% rename from tests/unit/nativeVersionUpdaterTest.js rename to tests/unit/nativeVersionUpdaterTest.ts