diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index c971b80..a773d6f 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -58,24 +58,30 @@ jobs: exit 1 fi - test-front-desk: + test-front: runs-on: ubuntu-latest defaults: run: - working-directory: src/frontend/apps/desk + working-directory: src/frontend/ steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18.x' cache: 'yarn' cache-dependency-path: src/frontend/yarn.lock + - name: Install dependencies run: yarn install --frozen-lockfile - - name: Test Desk App - run: yarn test + + - name: Test App + run: yarn app:test + + - name: Test Translations + run: yarn i18n:test lint-front: runs-on: ubuntu-latest diff --git a/src/frontend/package.json b/src/frontend/package.json index 3eba1da..57a6dd8 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -20,7 +20,8 @@ "e2e:test": "yarn APP_E2E run test", "lint": "yarn APP_DESK run lint && yarn APP_E2E run lint", "i18n:extract": "yarn I18N run extract-translation", - "i18n:deploy": "yarn I18N run format-deploy" + "i18n:deploy": "yarn I18N run format-deploy", + "i18n:test": "yarn I18N run test" }, "resolutions": { "@types/node": "20.11.16", diff --git a/src/frontend/packages/i18n/__tests__/i18n.test.ts b/src/frontend/packages/i18n/__tests__/i18n.test.ts new file mode 100644 index 0000000..0c81ba7 --- /dev/null +++ b/src/frontend/packages/i18n/__tests__/i18n.test.ts @@ -0,0 +1,104 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +describe('integration testing on i18n package', () => { + afterAll(() => { + fs.rmSync('./locales/tests', { recursive: true, force: true }); + }); + + test('cmd extract-translation:desk', () => { + // To be sure the file is not here + fs.rmSync('./locales/desk/translations-crowdin.json', { + recursive: true, + force: true, + }); + expect( + fs.existsSync('./locales/desk/translations-crowdin.json'), + ).toBeFalsy(); + + // Generate the file + execSync('yarn extract-translation:desk'); + expect( + fs.existsSync('./locales/desk/translations-crowdin.json'), + ).toBeTruthy(); + }); + + test('cmd format-deploy', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/en/', { recursive: true }); + fs.writeFileSync( + './locales/tests/en/translations.json', + JSON.stringify({ test: { message: 'My test' } }), + 'utf8', + ); + expect(fs.existsSync('./locales/tests/en/translations.json')).toBeTruthy(); + + fs.mkdirSync('./locales/tests/fr/', { recursive: true }); + fs.writeFileSync( + './locales/tests/fr/translations.json', + JSON.stringify({ test: { message: 'Mon test' } }), + 'utf8', + ); + expect(fs.existsSync('./locales/tests/fr/translations.json')).toBeTruthy(); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`); + const json = JSON.parse(fs.readFileSync(output, 'utf8')); + expect(json).toEqual({ + en: { + translation: { test: 'My test' }, + }, + fr: { + translation: { test: 'Mon test' }, + }, + }); + }); + + test('cmd format-deploy throws an error when translation file is not found', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/en/', { recursive: true }); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + + const cmd = () => { + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`, { + stdio: 'pipe', + }); + }; + + expect(cmd).toThrow( + `Error: File locales${path.sep}tests${path.sep}en${path.sep}translations.json not found!`, + ); + }); + + test('cmd format-deploy throws an error when no translation to deploy', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/', { recursive: true }); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + + const cmd = () => { + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`, { + stdio: 'pipe', + }); + }; + + expect(cmd).toThrow('Error: No translation to deploy'); + }); +}); diff --git a/src/frontend/packages/i18n/__tests__/translations.test.ts b/src/frontend/packages/i18n/__tests__/translations.test.ts new file mode 100644 index 0000000..7a56058 --- /dev/null +++ b/src/frontend/packages/i18n/__tests__/translations.test.ts @@ -0,0 +1,28 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; + +describe('checks all the frontend translation are made', () => { + it('checks missing translation. If this test fails, go to https://crowdin.com/', () => { + // Extract the translations + execSync( + 'yarn extract-translation:desk -c ./i18next-parser.config.jest.mjs', + ); + const outputCrowdin = './locales/desk/translations-crowdin.json'; + const jsonCrowdin = JSON.parse(fs.readFileSync(outputCrowdin, 'utf8')); + const listKeysCrowdin = Object.keys(jsonCrowdin).sort(); + + // Check the translations in the app Desk + const outputDesk = '../../apps/desk/src/i18n/translations.json'; + const jsonDesk = JSON.parse(fs.readFileSync(outputDesk, 'utf8')); + + // Our keys are in english, so we don't need to check the english translation + Object.keys(jsonDesk) + .filter((key) => key !== 'en') + .forEach((key) => { + const listKeysDesk = Object.keys(jsonDesk[key].translation).sort(); + expect( + listKeysCrowdin.every((element) => listKeysDesk.includes(element)), + ).toBeTruthy(); + }); + }); +}); diff --git a/src/frontend/packages/i18n/format-deploy.mjs b/src/frontend/packages/i18n/format-deploy.mjs index 1cead3a..0d410f0 100644 --- a/src/frontend/packages/i18n/format-deploy.mjs +++ b/src/frontend/packages/i18n/format-deploy.mjs @@ -28,7 +28,7 @@ fs.readdirSync(folderPath).map((language) => { const pathTranslateFile = path.join(languagePath, path.sep, namefile); if (!fs.existsSync(pathTranslateFile)) { - return; + throw new Error(`File ${pathTranslateFile} not found!`); } const json = JSON.parse(fs.readFileSync(pathTranslateFile, "utf8")); @@ -43,6 +43,10 @@ fs.readdirSync(folderPath).map((language) => { }; }); +if (!Object.keys(jsonI18n).length) { + throw new Error(`No translation to deploy`); +} + // Write the file to the output fs.writeFileSync(output, JSON.stringify(jsonI18n), "utf8"); diff --git a/src/frontend/packages/i18n/jest.config.ts b/src/frontend/packages/i18n/jest.config.ts new file mode 100644 index 0000000..8b55364 --- /dev/null +++ b/src/frontend/packages/i18n/jest.config.ts @@ -0,0 +1,7 @@ +export default { + rootDir: "./", + testEnvironment: "node", + transform: { + "^.+\\.(ts)$": "ts-jest", + }, +}; diff --git a/src/frontend/packages/i18n/package.json b/src/frontend/packages/i18n/package.json index 53f74fe..c080417 100644 --- a/src/frontend/packages/i18n/package.json +++ b/src/frontend/packages/i18n/package.json @@ -6,10 +6,16 @@ "extract-translation": "yarn extract-translation:desk", "extract-translation:desk": "yarn i18next ../../apps/desk/**/*.{ts,tsx} -c ./i18next-parser.config.mjs -o ./locales/desk/translations-crowdin.json", "format-deploy": "yarn format-deploy:desk", - "format-deploy:desk": "node ./format-deploy.mjs --app=desk --output=../../apps/desk/src/i18n/translations.json" + "format-deploy:desk": "node ./format-deploy.mjs --app=desk --output=../../apps/desk/src/i18n/translations.json", + "test": "jest" }, "dependencies": { + "@types/jest": "29.5.11", + "@types/node": "20.11.6", "i18next-parser": "8.8.0", + "jest": "29.7.0", + "ts-jest": "29.1.2", + "typescript": "*", "yargs": "17.7.2" } } diff --git a/src/frontend/packages/i18n/tsconfig.json b/src/frontend/packages/i18n/tsconfig.json new file mode 100644 index 0000000..c4350f5 --- /dev/null +++ b/src/frontend/packages/i18n/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + }, + "include": [ + "**/*.ts", + ], + "exclude": ["node_modules"] +} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 9e67c17..4a1ef18 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -2295,6 +2295,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@29.5.11": + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jest@29.5.12": version "29.5.12" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" @@ -2327,7 +2335,7 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== -"@types/node@*", "@types/node@20.11.16": +"@types/node@*", "@types/node@20.11.16", "@types/node@20.11.6": version "20.11.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" integrity sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ== @@ -2984,6 +2992,13 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4141,7 +4156,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -5455,7 +5470,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -5749,6 +5764,11 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -5807,7 +5827,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -7291,6 +7311,20 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== +ts-jest@29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" + integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + ts-node@10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -7797,7 +7831,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.1.1: +yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==