diff --git a/schema.json b/schema.json index 210c5abc..5d2c79c5 100644 --- a/schema.json +++ b/schema.json @@ -165,8 +165,15 @@ "type": "boolean" }, "removeUnused": { - "description": "Delete unused keys from the Tolgee project", + "description": "Delete unused keys from the Tolgee project (within selected namespaces if specified).", "type": "boolean" + }, + "namespaces": { + "description": "Specifies which namespaces should be synchronized.", + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/src/commands/sync/sync.ts b/src/commands/sync/sync.ts index 5f3af338..dd5d0849 100644 --- a/src/commands/sync/sync.ts +++ b/src/commands/sync/sync.ts @@ -24,6 +24,7 @@ type Options = BaseOptions & { backup?: string | false; removeUnused?: boolean; continueOnWarning?: boolean; + namespaces?: string[]; yes?: boolean; tagNewKeys?: string[]; }; @@ -80,6 +81,15 @@ const syncHandler = (config: Schema) => } const localKeys = filterExtractionResult(rawKeys); + + if (opts.namespaces?.length) { + for (const namespace of Object.keys(localKeys)) { + if (!opts.namespaces?.includes(namespace)) { + localKeys[namespace].clear(); + } + } + } + const allKeysLoadable = await opts.client.GET( '/v2/projects/{projectId}/all-keys', { @@ -89,9 +99,16 @@ const syncHandler = (config: Schema) => handleLoadableError(allKeysLoadable); - const remoteKeys = allKeysLoadable.data?._embedded?.keys ?? []; + let remoteKeys = allKeysLoadable.data?._embedded?.keys ?? []; + + if (opts.namespaces?.length) { + remoteKeys = remoteKeys.filter((key) => { + return opts.namespaces?.includes(key.namespace ?? ''); + }); + } const diff = compareKeys(localKeys, remoteKeys); + if (!diff.added.length && !diff.removed.length) { console.log( ansi.green( @@ -223,6 +240,12 @@ export default (config: Schema) => 'Set this flag to continue the sync if warnings are detected during string extraction. By default, as warnings may indicate an invalid extraction, the CLI will abort the sync.' ).default(config.sync?.continueOnWarning ?? false) ) + .addOption( + new Option( + '-n, --namespaces ', + 'Specifies which namespaces should be synchronized.' + ).default(config.sync?.namespaces) + ) .addOption( new Option( '-Y, --yes', @@ -232,7 +255,7 @@ export default (config: Schema) => .addOption( new Option( '--remove-unused', - 'Delete unused keys from the Tolgee project.' + 'Delete unused keys from the Tolgee project (within selected namespaces if specified).' ).default(config.sync?.removeUnused ?? false) ) .option( diff --git a/src/commands/sync/syncUtils.ts b/src/commands/sync/syncUtils.ts index fa7ba53c..e81c1325 100644 --- a/src/commands/sync/syncUtils.ts +++ b/src/commands/sync/syncUtils.ts @@ -1,6 +1,6 @@ import type { ResponseOf } from '../../client/internal/schema.utils.js'; import type { Key } from '../../extractor/index.js'; -import { type FilteredKeys, NullNamespace } from '../../extractor/runner.js'; +import { type FilteredKeys } from '../../extractor/runner.js'; import ansi from 'ansi-colors'; type ResponseAllKeys = ResponseOf< @@ -54,19 +54,19 @@ export function compareKeys( // Deleted keys for (const remoteKey of remote) { - const namespace = remoteKey.namespace || NullNamespace; + const namespace = remoteKey.namespace || ''; const keyExists = local[namespace]?.delete(remoteKey.name); if (!keyExists) { result.removed.push({ id: remoteKey.id, keyName: remoteKey.name, - namespace: remoteKey.namespace || undefined, + namespace: remoteKey.namespace || '', }); } } // Added keys - const namespaces = [NullNamespace, ...Object.keys(local).sort()] as const; + const namespaces = [...Object.keys(local).sort()] as const; for (const namespace of namespaces) { if (namespace in local && local[namespace].size) { const keys = local[namespace]; @@ -74,7 +74,7 @@ export function compareKeys( for (const keyName of keyNames) { result.added.push({ keyName: keyName, - namespace: namespace === NullNamespace ? undefined : namespace, + namespace: namespace || '', defaultValue: keys.get(keyName) || undefined, }); } diff --git a/src/extractor/runner.ts b/src/extractor/runner.ts index 24967509..4fd482fd 100644 --- a/src/extractor/runner.ts +++ b/src/extractor/runner.ts @@ -10,10 +10,7 @@ import { extname } from 'path'; import { callWorker } from './worker.js'; import { exitWithError } from '../utils/logger.js'; -export const NullNamespace = Symbol('namespace.null'); - export type FilteredKeys = { - [NullNamespace]: Map; [key: string]: Map; }; @@ -142,7 +139,7 @@ export function filterExtractionResult(data: ExtractionResults): FilteredKeys { const result: FilteredKeys = Object.create(null); for (const { keys } of data.values()) { for (const key of keys) { - const namespace = key.namespace || NullNamespace; + const namespace = key.namespace || ''; if (!(namespace in result)) { result[namespace] = new Map(); } diff --git a/src/schema.d.ts b/src/schema.d.ts index 67abdbff..c1a07243 100644 --- a/src/schema.d.ts +++ b/src/schema.d.ts @@ -193,6 +193,10 @@ export interface Schema { * Delete unused keys from the Tolgee project */ removeUnused?: boolean; + /** + * Specifies which namespaces should be synchronized. + */ + namespaces?: string[]; }; tag?: { /** diff --git a/test/__fixtures__/testProjectCode/Test3Mixed.tsx b/test/__fixtures__/testProjectCode/Test3Mixed.tsx index 2f324f37..8ab78cb9 100644 --- a/test/__fixtures__/testProjectCode/Test3Mixed.tsx +++ b/test/__fixtures__/testProjectCode/Test3Mixed.tsx @@ -7,7 +7,9 @@ export default function App() {

- Welcome! + + Welcome! +

  • @@ -17,13 +19,7 @@ export default function App() {
  • - -
  • -
  • - -
  • -
  • - +
@@ -44,9 +40,6 @@ export default function App() {
  • -
  • - -
  • diff --git a/test/e2e/compare.test.ts b/test/e2e/compare.test.ts index ba7d1380..c16107b0 100644 --- a/test/e2e/compare.test.ts +++ b/test/e2e/compare.test.ts @@ -173,9 +173,10 @@ describe('Project 3', () => { expect(out.code).toBe(0); expect(out.stdout).toContain('out of sync'); - expect(out.stdout).toContain('4 new keys found'); - expect(out.stdout).toContain('3 unused keys'); + expect(out.stdout).toContain('5 new keys found'); + expect(out.stdout).toContain('7 unused keys'); expect(out.stdout).toContain('+ cookies (namespace: food)'); + expect(out.stdout).toContain('- onions (namespace: food)'); expect(out.stdout).toContain('- soda (namespace: drinks)'); expect(out.stdout).toContain('+ table (namespace: furniture)'); expect(out.stdout).toContain('- table\n'); diff --git a/test/e2e/push.p1.test.ts b/test/e2e/push.p1.test.ts new file mode 100644 index 00000000..c196cc14 --- /dev/null +++ b/test/e2e/push.p1.test.ts @@ -0,0 +1,133 @@ +import { fileURLToPath } from 'node:url'; +import { tolgeeDataToDict } from './utils/data.js'; +import { run } from './utils/run.js'; +import { + createPak, + createProjectWithClient, + deleteProject, +} from './utils/api/common.js'; +import { TolgeeClient } from '#cli/client/TolgeeClient.js'; +import { PROJECT_1 } from './utils/api/project1.js'; +import { createTmpFolderWithConfig, removeTmpFolder } from './utils/tmp.js'; +import { pushFilesConfig } from './utils/pushFilesConfig.js'; + +const FIXTURES_PATH = new URL('../__fixtures__/', import.meta.url); + +const PROJECT_1_DIR = new URL('./updatedProject1/', FIXTURES_PATH); + +let client: TolgeeClient; +let pak: string; + +describe('project 1', () => { + beforeEach(async () => { + client = await createProjectWithClient('Project 1', PROJECT_1); + pak = await createPak(client); + }); + afterEach(async () => { + await deleteProject(client); + await removeTmpFolder(); + }); + + it('pushes updated strings to Tolgee', async () => { + const out = await run([ + '--api-key', + pak, + 'push', + '--verbose', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_1_DIR)), + ]); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { search: 'wire' }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + wired: { + __ns: null, + en: 'Wired', + fr: 'Filaire', + }, + wireless: { + __ns: null, + en: 'Wireless', + fr: 'Sans-fil', + }, + }); + }); + + it('pushes only selected languages (args)', async () => { + const out = await run([ + '--api-key', + pak, + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_1_DIR)), + '-l', + 'fr', + ]); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { search: 'wire' }, + }, + }); + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + wired: { + __ns: null, + fr: 'Filaire', + }, + wireless: { + __ns: null, + fr: 'Sans-fil', + }, + }); + }); + + it('pushes only selected languages (config)', async () => { + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pak, + push: { + files: pushFilesConfig(PROJECT_1_DIR), + languages: ['fr'], + }, + }); + const out = await run(['--config', configFile, 'push']); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { search: 'wire' }, + }, + }); + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + wired: { + __ns: null, + fr: 'Filaire', + }, + wireless: { + __ns: null, + fr: 'Sans-fil', + }, + }); + }); +}); diff --git a/test/e2e/push.p2.test.ts b/test/e2e/push.p2.test.ts new file mode 100644 index 00000000..cde11948 --- /dev/null +++ b/test/e2e/push.p2.test.ts @@ -0,0 +1,264 @@ +import { fileURLToPath } from 'node:url'; +import { tolgeeDataToDict } from './utils/data.js'; +import { run, runWithStdin } from './utils/run.js'; +import { + createPak, + createProjectWithClient, + deleteProject, +} from './utils/api/common.js'; +import { TolgeeClient } from '#cli/client/TolgeeClient.js'; +import { PROJECT_2 } from './utils/api/project2.js'; +import { createTmpFolderWithConfig, removeTmpFolder } from './utils/tmp.js'; + +const FIXTURES_PATH = new URL('../__fixtures__/', import.meta.url); + +const PROJECT_2_DIR = new URL('./updatedProject2WithConflicts/', FIXTURES_PATH); + +let client: TolgeeClient; +let pak: string; + +describe('project 2', () => { + beforeEach(async () => { + client = await createProjectWithClient('Project 2', PROJECT_2); + pak = await createPak(client); + }); + afterEach(async () => { + await deleteProject(client); + await removeTmpFolder(); + }); + + it('does not push strings to Tolgee if there are conflicts', async () => { + const { configFile } = await createTmpFolderWithConfig({ + push: { + filesTemplate: fileURLToPath( + new URL(`./{languageTag}.json`, PROJECT_2_DIR) + ), + }, + }); + const out = await run([ + '--config', + configFile, + 'push', + '--api-key', + pak, + '--verbose', + ]); + + expect(out.code).toBe(1); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(1); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Cat', + fr: 'Chat', + }, + }); + }); + + it('does preserve the remote strings when using KEEP (args)', async () => { + const out = await run([ + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_2_DIR)), + '--languages', + 'en', + 'fr', + '--api-key', + pak, + '--force-mode', + 'KEEP', + ]); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Cat', + fr: 'Chat', + }, + 'fox-name': { + __ns: null, + en: 'Fox', + fr: 'Renard', + }, + }); + }); + + it('does preserve the remote strings when using KEEP (config)', async () => { + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pak, + push: { + filesTemplate: fileURLToPath( + new URL(`./{languageTag}.json`, PROJECT_2_DIR) + ), + forceMode: 'KEEP', + languages: ['en', 'fr'], + }, + }); + const out = await run(['--config', configFile, 'push']); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Cat', + fr: 'Chat', + }, + 'fox-name': { + __ns: null, + en: 'Fox', + fr: 'Renard', + }, + }); + }); + + it('asks for confirmation when there are conflicts', async () => { + const out = await runWithStdin( + [ + 'push', + '--api-key', + pak, + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_2_DIR)), + '--languages', + 'en', + 'fr', + ], + 'OVERRIDE' + ); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Kitty', + fr: 'Chaton', + }, + 'fox-name': { + __ns: null, + en: 'Fox', + fr: 'Renard', + }, + }); + }); + + it('does override the remote strings when using OVERRIDE (args)', async () => { + const out = await run([ + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_2_DIR)), + '--languages', + 'en', + 'fr', + '--api-key', + pak, + '--force-mode', + 'OVERRIDE', + ]); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Kitty', + fr: 'Chaton', + }, + 'fox-name': { + __ns: null, + en: 'Fox', + fr: 'Renard', + }, + }); + }); + + it('does override the remote strings when using OVERRIDE (config)', async () => { + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pak, + push: { + filesTemplate: fileURLToPath( + new URL(`./{languageTag}.json`, PROJECT_2_DIR) + ), + forceMode: 'OVERRIDE', + languages: ['en', 'fr'], + }, + }); + const out = await run(['--config', configFile, 'push']); + + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['cat-name', 'fox-name'] }, + }, + }); + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + 'cat-name': { + __ns: null, + en: 'Kitty', + fr: 'Chaton', + }, + 'fox-name': { + __ns: null, + en: 'Fox', + fr: 'Renard', + }, + }); + }); +}); diff --git a/test/e2e/push.p3.test.ts b/test/e2e/push.p3.test.ts new file mode 100644 index 00000000..a4304247 --- /dev/null +++ b/test/e2e/push.p3.test.ts @@ -0,0 +1,285 @@ +import { fileURLToPath } from 'node:url'; +import { tolgeeDataToDict } from './utils/data.js'; +import { run } from './utils/run.js'; +import { + DEFAULT_SCOPES, + createPak, + createProjectWithClient, + deleteProject, +} from './utils/api/common.js'; +import { TolgeeClient } from '#cli/client/TolgeeClient.js'; +import { PROJECT_3 } from './utils/api/project3.js'; +import { createTmpFolderWithConfig, removeTmpFolder } from './utils/tmp.js'; + +const FIXTURES_PATH = new URL('../__fixtures__/', import.meta.url); + +const PROJECT_3_DIR = new URL('./updatedProject3/', FIXTURES_PATH); +const PROJECT_3_DEPRECATED_DIR = new URL( + './updatedProject3DeprecatedKeys/', + FIXTURES_PATH +); + +let client: TolgeeClient; +let pak: string; + +describe('project 3', () => { + beforeEach(async () => { + client = await createProjectWithClient('Project 3', PROJECT_3, { + icuEnabled: true, + }); + pak = await createPak(client); + }); + afterEach(async () => { + await deleteProject(client); + await removeTmpFolder(); + }); + + it('pushes to Tolgee with correct namespaces', async () => { + const out = await run([ + '--api-key', + pak, + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), + fileURLToPath(new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR)), + '--force-mode', + 'OVERRIDE', + ]); + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['water', 'glass'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(2); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + glass: { + __ns: null, + en: 'Glass', + fr: 'Verre', + }, + water: { + __ns: 'drinks', + en: 'Dihydrogen monoxide', + fr: 'Monoxyde de dihydrogène', + }, + }); + }); + + it('pushes only selected namespaces and languages (args)', async () => { + const out = await run([ + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), + fileURLToPath(new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR)), + '--api-key', + pak, + '--force-mode', + 'override', + '-n', + 'drinks', + ]); + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['water', 'glass'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(1); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + water: { + __ns: 'drinks', + en: 'Dihydrogen monoxide', + fr: 'Monoxyde de dihydrogène', + }, + }); + }); + + it('pushes only selected namespaces and languages (config)', async () => { + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pak, + push: { + filesTemplate: [ + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), + fileURLToPath( + new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR) + ), + ], + forceMode: 'OVERRIDE', + namespaces: ['drinks'], + }, + }); + const out = await run(['--config', configFile, 'push']); + expect(out.code).toBe(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['water', 'glass'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(1); + + const stored = tolgeeDataToDict(keys.data); + expect(stored).toEqual({ + water: { + __ns: 'drinks', + en: 'Dihydrogen monoxide', + fr: 'Monoxyde de dihydrogène', + }, + }); + }); + + it('removes other keys (args)', async () => { + const pakWithDelete = await createPak(client, [ + ...DEFAULT_SCOPES, + 'keys.delete', + ]); + + const out = await run([ + 'push', + '--files-template', + fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DEPRECATED_DIR)), + fileURLToPath( + new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DEPRECATED_DIR) + ), + '--api-key', + pakWithDelete, + '--remove-other-keys', + ]); + + expect(out.code).toEqual(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + // Keys in the "food" namespace should not be removed + expect(Object.keys(stored)).toEqual([ + 'table', + 'chair', + 'plate', + 'fork', + 'water', + 'salad', + 'tomato', + 'onions', + ]); + }); + + it('removes other keys (config)', async () => { + const pakWithDelete = await createPak(client, [ + ...DEFAULT_SCOPES, + 'keys.delete', + ]); + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pakWithDelete, + push: { + filesTemplate: [ + fileURLToPath( + new URL(`./{languageTag}.json`, PROJECT_3_DEPRECATED_DIR) + ), + fileURLToPath( + new URL( + `./{namespace}/{languageTag}.json`, + PROJECT_3_DEPRECATED_DIR + ) + ), + ], + removeOtherKeys: true, + }, + }); + const out = await run(['--config', configFile, 'push']); + + expect(out.code).toEqual(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + // Keys in the "food" namespace should not be removed + expect(Object.keys(stored)).toEqual([ + 'table', + 'chair', + 'plate', + 'fork', + 'water', + 'salad', + 'tomato', + 'onions', + ]); + }); + + it("doesn't remove other keys when filtered by namespace", async () => { + const pakWithDelete = await createPak(client, [ + ...DEFAULT_SCOPES, + 'keys.delete', + ]); + const { configFile } = await createTmpFolderWithConfig({ + push: { + filesTemplate: [ + fileURLToPath( + new URL(`./{languageTag}.json`, PROJECT_3_DEPRECATED_DIR) + ), + fileURLToPath( + new URL( + `./{namespace}/{languageTag}.json`, + PROJECT_3_DEPRECATED_DIR + ) + ), + ], + }, + }); + const out = await run([ + '--config', + configFile, + 'push', + '--api-key', + pakWithDelete, + '--remove-other-keys', + '--namespaces', + 'drinks', + ]); + + expect(out.code).toEqual(0); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + expect(Object.keys(stored)).toEqual([ + 'table', + 'chair', + 'plate', + 'fork', + 'knife', + 'water', + 'salad', + 'tomato', + 'onions', + ]); + }); +}); diff --git a/test/e2e/push.test.ts b/test/e2e/push.test.ts deleted file mode 100644 index 0b539c49..00000000 --- a/test/e2e/push.test.ts +++ /dev/null @@ -1,715 +0,0 @@ -import { fileURLToPath } from 'url'; -import { tolgeeDataToDict } from './utils/data.js'; -import { run, runWithStdin } from './utils/run.js'; -import { - DEFAULT_SCOPES, - createPak, - createProjectWithClient, - deleteProject, -} from './utils/api/common.js'; -import { TolgeeClient } from '#cli/client/TolgeeClient.js'; -import { PROJECT_1 } from './utils/api/project1.js'; -import { PROJECT_3 } from './utils/api/project3.js'; -import { PROJECT_2 } from './utils/api/project2.js'; -import { createTmpFolderWithConfig, removeTmpFolder } from './utils/tmp.js'; - -const FIXTURES_PATH = new URL('../__fixtures__/', import.meta.url); - -const PROJECT_1_DIR = new URL('./updatedProject1/', FIXTURES_PATH); - -const PROJECT_2_DIR = new URL('./updatedProject2WithConflicts/', FIXTURES_PATH); -const PROJECT_3_DIR = new URL('./updatedProject3/', FIXTURES_PATH); -const PROJECT_3_DEPRECATED_DIR = new URL( - './updatedProject3DeprecatedKeys/', - FIXTURES_PATH -); - -let client: TolgeeClient; -let pak: string; - -describe('project 1', () => { - beforeEach(async () => { - client = await createProjectWithClient('Project 1', PROJECT_1); - pak = await createPak(client); - }); - afterEach(async () => { - await deleteProject(client); - await removeTmpFolder(); - }); - - it('pushes updated strings to Tolgee', async () => { - const out = await run([ - '--api-key', - pak, - 'push', - '--verbose', - '--files-template', - fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_1_DIR)), - ]); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { search: 'wire' }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - wired: { - __ns: null, - en: 'Wired', - fr: 'Filaire', - }, - wireless: { - __ns: null, - en: 'Wireless', - fr: 'Sans-fil', - }, - }); - }); - - it('pushes only selected languages (args)', async () => { - const out = await run([ - '--api-key', - pak, - 'push', - '-l', - 'fr', - '--files-template', - fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_1_DIR)), - ]); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { search: 'wire' }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - wired: { - __ns: null, - fr: 'Filaire', - }, - wireless: { - __ns: null, - fr: 'Sans-fil', - }, - }); - }); - - it('pushes only selected languages (config)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_1_DIR) - ), - languages: ['fr'], - }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { search: 'wire' }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - wired: { - __ns: null, - fr: 'Filaire', - }, - wireless: { - __ns: null, - fr: 'Sans-fil', - }, - }); - }); -}); - -describe('project 3', () => { - beforeEach(async () => { - client = await createProjectWithClient('Project 3', PROJECT_3, { - icuEnabled: true, - }); - pak = await createPak(client); - }); - afterEach(async () => { - await deleteProject(client); - await removeTmpFolder(); - }); - - it('pushes to Tolgee with correct namespaces', async () => { - const out = await run([ - '--api-key', - pak, - 'push', - '--files-template', - fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), - fileURLToPath(new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR)), - '--force-mode', - 'OVERRIDE', - ]); - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['water', 'glass'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - glass: { - __ns: null, - en: 'Glass', - fr: 'Verre', - }, - water: { - __ns: 'drinks', - en: 'Dihydrogen monoxide', - fr: 'Monoxyde de dihydrogène', - }, - }); - }); - - it('pushes only selected namespaces and languages (args)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: [ - fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), - fileURLToPath( - new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR) - ), - ], - forceMode: 'OVERRIDE', - }, - }); - const out = await run([ - '--config', - configFile, - 'push', - '--api-key', - pak, - '--force-mode', - 'override', - '-n', - 'drinks', - ]); - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['water', 'glass'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(1); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - water: { - __ns: 'drinks', - en: 'Dihydrogen monoxide', - fr: 'Monoxyde de dihydrogène', - }, - }); - }); - - it('pushes only selected namespaces and languages (config)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { - filesTemplate: [ - fileURLToPath(new URL(`./{languageTag}.json`, PROJECT_3_DIR)), - fileURLToPath( - new URL(`./{namespace}/{languageTag}.json`, PROJECT_3_DIR) - ), - ], - forceMode: 'OVERRIDE', - namespaces: ['drinks'], - }, - }); - const out = await run(['--config', configFile, 'push']); - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['water', 'glass'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(1); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - water: { - __ns: 'drinks', - en: 'Dihydrogen monoxide', - fr: 'Monoxyde de dihydrogène', - }, - }); - }); - - it('removes other keys (args)', async () => { - const pakWithDelete = await createPak(client, [ - ...DEFAULT_SCOPES, - 'keys.delete', - ]); - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: [ - fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_3_DEPRECATED_DIR) - ), - fileURLToPath( - new URL( - `./{namespace}/{languageTag}.json`, - PROJECT_3_DEPRECATED_DIR - ) - ), - ], - }, - }); - const out = await run([ - '--config', - configFile, - 'push', - '--api-key', - pakWithDelete, - '--remove-other-keys', - ]); - - expect(out.code).toEqual(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - }, - }); - - const stored = tolgeeDataToDict(keys.data); - - // Keys in the "food" namespace should not be removed - expect(Object.keys(stored)).toEqual([ - 'table', - 'chair', - 'plate', - 'fork', - 'water', - 'salad', - 'tomato', - 'onions', - ]); - }); - - it('removes other keys (config)', async () => { - const pakWithDelete = await createPak(client, [ - ...DEFAULT_SCOPES, - 'keys.delete', - ]); - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pakWithDelete, - push: { - filesTemplate: [ - fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_3_DEPRECATED_DIR) - ), - fileURLToPath( - new URL( - `./{namespace}/{languageTag}.json`, - PROJECT_3_DEPRECATED_DIR - ) - ), - ], - removeOtherKeys: true, - }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toEqual(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - }, - }); - - const stored = tolgeeDataToDict(keys.data); - - // Keys in the "food" namespace should not be removed - expect(Object.keys(stored)).toEqual([ - 'table', - 'chair', - 'plate', - 'fork', - 'water', - 'salad', - 'tomato', - 'onions', - ]); - }); -}); - -describe('project 2', () => { - beforeEach(async () => { - client = await createProjectWithClient('Project 2', PROJECT_2); - pak = await createPak(client); - }); - afterEach(async () => { - await deleteProject(client); - await removeTmpFolder(); - }); - - it('does not push strings to Tolgee if there are conflicts', async () => { - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - }, - }); - const out = await run([ - '--config', - configFile, - 'push', - '--api-key', - pak, - '--verbose', - ]); - - expect(out.code).toBe(1); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(1); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Cat', - fr: 'Chat', - }, - }); - }); - - it('does preserve the remote strings when using KEEP (args)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - languages: ['en', 'fr'], - }, - }); - const out = await run([ - '--config', - configFile, - 'push', - '--api-key', - pak, - '--force-mode', - 'KEEP', - ]); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Cat', - fr: 'Chat', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('does preserve the remote strings when using KEEP (config)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - forceMode: 'KEEP', - languages: ['en', 'fr'], - }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Cat', - fr: 'Chat', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('asks for confirmation when there are conflicts', async () => { - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - languages: ['en', 'fr'], - }, - }); - const out = await runWithStdin( - ['--config', configFile, 'push', '--api-key', pak], - 'OVERRIDE' - ); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Kitty', - fr: 'Chaton', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('does override the remote strings when using OVERRIDE (args)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - languages: ['en', 'fr'], - }, - }); - const out = await run([ - '--config', - configFile, - 'push', - '--api-key', - pak, - '--force-mode', - 'OVERRIDE', - ]); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Kitty', - fr: 'Chaton', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('does override the remote strings when using OVERRIDE (config)', async () => { - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { - filesTemplate: fileURLToPath( - new URL(`./{languageTag}.json`, PROJECT_2_DIR) - ), - forceMode: 'OVERRIDE', - languages: ['en', 'fr'], - }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Kitty', - fr: 'Chaton', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('does push correctly when language is not specified', async () => { - const files = [ - { path: fileURLToPath(new URL(`./en.json`, PROJECT_2_DIR)) }, - { path: fileURLToPath(new URL(`./fr.json`, PROJECT_2_DIR)) }, - ]; - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { files, forceMode: 'OVERRIDE' }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Kitty', - fr: 'Chaton', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - fr: 'Renard', - }, - }); - }); - - it('does push only selected languages even if langauges are not specified', async () => { - const files = [ - { path: fileURLToPath(new URL(`./en.json`, PROJECT_2_DIR)) }, - { path: fileURLToPath(new URL(`./fr.json`, PROJECT_2_DIR)) }, - ]; - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { files, forceMode: 'OVERRIDE' }, - }); - const out = await run(['--config', configFile, 'push', '-l', 'en']); - - expect(out.code).toBe(0); - - const keys = await client.GET('/v2/projects/{projectId}/translations', { - params: { - path: { projectId: client.getProjectId() }, - query: { filterKeyName: ['cat-name', 'fox-name'] }, - }, - }); - expect(keys.data?.page?.totalElements).toBe(2); - - const stored = tolgeeDataToDict(keys.data); - expect(stored).toEqual({ - 'cat-name': { - __ns: null, - en: 'Kitty', - fr: 'Chat', - }, - 'fox-name': { - __ns: null, - en: 'Fox', - }, - }); - }); - - it('does print a nice error when languages mapped incorrectly', async () => { - const files = [ - { path: fileURLToPath(new URL(`./invalid.json`, PROJECT_2_DIR)) }, - ]; - const { configFile } = await createTmpFolderWithConfig({ - apiKey: pak, - push: { files, forceMode: 'OVERRIDE' }, - }); - const out = await run(['--config', configFile, 'push']); - - expect(out.code).toBe(1); - expect(out.stdout).to.contain( - 'Not able to map files to existing languages in the platform' - ); - }); -}); diff --git a/test/e2e/sync.test.ts b/test/e2e/sync.test.ts index f2cced3f..153217d3 100644 --- a/test/e2e/sync.test.ts +++ b/test/e2e/sync.test.ts @@ -31,7 +31,8 @@ const CODE_PROJECT_2_COMPLETE = `${CODE_PATH}/Test2Complete.tsx`; const CODE_PROJECT_2_ADDED = `${CODE_PATH}/Test2New.tsx`; const CODE_PROJECT_2_DELETED = `${CODE_PATH}/Test2Incomplete.tsx`; const CODE_PROJECT_2_WARNING = `${CODE_PATH}/Test2Warning.tsx`; -const CODE_PROJECT_3 = `${CODE_PATH}/Test3SingleDiff.tsx`; +const CODE_PROJECT_3_DIFF = `${CODE_PATH}/Test3SingleDiff.tsx`; +const CODE_PROJECT_3_MIXED = `${CODE_PATH}/Test3Mixed.tsx`; setupTemporaryFolder(); @@ -143,7 +144,7 @@ describe('Project 2', () => { }); }, 30e3); - it('deletes keys that no longer exist via --remove-unused', async () => { + it('deletes keys that no longer exist (args)', async () => { const pakWithDelete = await createPak(client, [ ...DEFAULT_SCOPES, 'keys.delete', @@ -176,7 +177,7 @@ describe('Project 2', () => { expect(keys.data?.page?.totalElements).toBe(0); }, 30e3); - it('deletes keys that no longer exist via config', async () => { + it('deletes keys that no longer exist (config)', async () => { const pakWithDelete = await createPak(client, [ ...DEFAULT_SCOPES, 'keys.delete', @@ -296,7 +297,7 @@ describe('Project 3', () => { it('handles namespaces properly (args)', async () => { const out = await run( - ['sync', '--yes', '--api-key', pak, '--patterns', CODE_PROJECT_3], + ['sync', '--yes', '--api-key', pak, '--patterns', CODE_PROJECT_3_DIFF], undefined, 20e3 ); @@ -325,7 +326,7 @@ describe('Project 3', () => { it('handles namespaces properly (config)', async () => { const { configFile } = await createTmpFolderWithConfig({ apiKey: pak, - patterns: [CODE_PROJECT_3], + patterns: [CODE_PROJECT_3_DIFF], }); const out = await run(['-c', configFile, 'sync', '--yes'], undefined, 20e3); @@ -349,4 +350,138 @@ describe('Project 3', () => { }, }); }, 30e3); + + it('synchronizes the defined namespaces only (args)', async () => { + const out = await run( + [ + 'sync', + '--yes', + '--api-key', + pak, + '--patterns', + CODE_PROJECT_3_MIXED, + '--namespaces', + 'food', + ], + undefined, + 20e3 + ); + + expect(out.code).toBe(0); + expect(out.stdout).toContain('+ 1 string'); + expect(out.stdout).toContain('1 unused key could be deleted.'); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + expect(Object.keys(stored)).toContain('table'); + expect(Object.keys(stored)).not.toContain('welcome'); + expect(Object.keys(stored).length).toEqual(11); + }, 30e3); + + it('synchronizes the defined namespaces only (config)', async () => { + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pak, + patterns: [CODE_PROJECT_3_MIXED], + sync: { + namespaces: ['', 'food'], + }, + }); + const out = await run(['-c', configFile, 'sync', '--yes'], undefined, 20e3); + + expect(out.code).toBe(0); + expect(out.stdout).toContain('+ 2 strings'); + expect(out.stdout).toContain('6 unused keys could be deleted.'); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + expect(Object.keys(stored)).toContain('spoon'); + expect(Object.keys(stored)).toContain('salad'); + expect(Object.keys(stored)).toContain('table'); + expect(Object.keys(stored)).not.toContain('welcome'); + expect(Object.keys(stored).length).toEqual(12); + }, 30e3); + + it('deletes only keys within namespace when using namespace selector (args)', async () => { + const pakWithDelete = await createPak(client, [ + ...DEFAULT_SCOPES, + 'keys.delete', + ]); + + const out = await run( + [ + 'sync', + '--yes', + '--remove-unused', + '--api-key', + pakWithDelete, + '--namespaces=', + '--namespaces=food', + '--patterns', + CODE_PROJECT_3_MIXED, + ], + undefined, + 20e3 + ); + + expect(out.code).toBe(0); + expect(out.stdout).toContain('+ 2 strings'); + expect(out.stdout).toContain('- 6 strings'); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + }, + }); + + const stored = tolgeeDataToDict(keys.data); + + expect(Object.keys(stored)).toContain('spoon'); + expect(Object.keys(stored)).toContain('salad'); + expect(Object.keys(stored)).not.toContain('table'); + expect(Object.keys(stored)).not.toContain('welcome'); + expect(Object.keys(stored).length).toEqual(6); + }, 30e3); + + it('deletes only keys within namespace when using namespace selector (config)', async () => { + const pakWithDelete = await createPak(client, [ + ...DEFAULT_SCOPES, + 'keys.delete', + ]); + + const { configFile } = await createTmpFolderWithConfig({ + apiKey: pakWithDelete, + patterns: [CODE_PROJECT_3_MIXED], + sync: { + namespaces: ['food'], + removeUnused: true, + }, + }); + + const out = await run(['-c', configFile, 'sync', '--yes'], undefined, 20e3); + + expect(out.code).toBe(0); + expect(out.stdout).toContain('- 1 string'); + expect(out.stdout).toContain('+ 1 string'); + + const keys = await client.GET('/v2/projects/{projectId}/translations', { + params: { + path: { projectId: client.getProjectId() }, + query: { filterKeyName: ['onions'] }, + }, + }); + + expect(keys.data?.page?.totalElements).toBe(0); + }, 30e3); }); diff --git a/test/e2e/utils/data.ts b/test/e2e/utils/data.ts index d77eb863..2fee49d7 100644 --- a/test/e2e/utils/data.ts +++ b/test/e2e/utils/data.ts @@ -1,6 +1,6 @@ export function tolgeeDataToDict(data: any) { return Object.fromEntries( - data._embedded.keys.map((k: any) => [ + (data._embedded?.keys ?? []).map((k: any) => [ k.keyName, { __ns: k.keyNamespace, diff --git a/test/e2e/utils/pushFilesConfig.ts b/test/e2e/utils/pushFilesConfig.ts new file mode 100644 index 00000000..0c2753d0 --- /dev/null +++ b/test/e2e/utils/pushFilesConfig.ts @@ -0,0 +1,20 @@ +import { FileMatch } from '#cli/schema.js'; +import { fileURLToPath } from 'url'; + +export function pushFilesConfig(base: URL, namespaces: string[] = ['']) { + const result: FileMatch[] = []; + + for (const ns of namespaces) { + result.push({ + path: fileURLToPath(new URL(`./${ns}/en.json`, base)), + language: 'en', + namespace: ns, + }); + result.push({ + path: fileURLToPath(new URL(`./${ns}/fr.json`, base)), + language: 'fr', + namespace: ns, + }); + } + return result; +}