@tolgee/core 4.7.0 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/tolgee.cjs.js +2 -4
  2. package/dist/tolgee.cjs.js.map +1 -1
  3. package/dist/tolgee.cjs.min.js +1 -1
  4. package/dist/tolgee.cjs.min.js.map +1 -1
  5. package/dist/{tolgee.esm.js → tolgee.esm.min.mjs} +2 -2
  6. package/dist/tolgee.esm.min.mjs.map +1 -0
  7. package/dist/tolgee.esm.mjs +5690 -0
  8. package/dist/tolgee.esm.mjs.map +1 -0
  9. package/dist/tolgee.umd.js +2 -4
  10. package/dist/tolgee.umd.js.map +1 -1
  11. package/dist/tolgee.umd.min.js +1 -1
  12. package/dist/tolgee.umd.min.js.map +1 -1
  13. package/package.json +10 -9
  14. package/src/Constants/Global.ts +9 -0
  15. package/src/Constants/ModifierKey.ts +6 -0
  16. package/src/Errors/ApiHttpError.ts +8 -0
  17. package/src/Observer.test.ts +119 -0
  18. package/src/Observer.ts +68 -0
  19. package/src/Properties.test.ts +150 -0
  20. package/src/Properties.ts +112 -0
  21. package/src/Tolgee.test.ts +473 -0
  22. package/src/Tolgee.ts +335 -0
  23. package/src/TolgeeConfig.test.ts +21 -0
  24. package/src/TolgeeConfig.ts +134 -0
  25. package/src/__integration/FormatterIcu.test.ts +80 -0
  26. package/src/__integration/FormatterMissing.ts +54 -0
  27. package/src/__integration/Tolgee.test.ts +90 -0
  28. package/src/__integration/TolgeeInvisible.test.ts +145 -0
  29. package/src/__integration/mockTranslations.ts +6 -0
  30. package/src/__integration/testConfig.ts +16 -0
  31. package/src/__testFixtures/classMock.ts +11 -0
  32. package/src/__testFixtures/createElement.ts +43 -0
  33. package/src/__testFixtures/createTestDom.ts +25 -0
  34. package/src/__testFixtures/mocked.ts +25 -0
  35. package/src/__testFixtures/setupAfterEnv.ts +34 -0
  36. package/src/helpers/NodeHelper.ts +90 -0
  37. package/src/helpers/TextHelper.test.ts +62 -0
  38. package/src/helpers/TextHelper.ts +58 -0
  39. package/src/helpers/commonTypes.ts +8 -0
  40. package/src/helpers/encoderPolyfill.ts +96 -0
  41. package/src/helpers/secret.test.ts +61 -0
  42. package/src/helpers/secret.ts +68 -0
  43. package/src/helpers/sleep.ts +2 -0
  44. package/src/highlighter/HighlightFunctionsInitializer.test.ts +40 -0
  45. package/src/highlighter/HighlightFunctionsInitializer.ts +61 -0
  46. package/src/highlighter/MouseEventHandler.test.ts +151 -0
  47. package/src/highlighter/MouseEventHandler.ts +191 -0
  48. package/src/highlighter/TranslationHighlighter.test.ts +177 -0
  49. package/src/highlighter/TranslationHighlighter.ts +113 -0
  50. package/src/index.ts +10 -0
  51. package/src/internal.ts +2 -0
  52. package/src/modules/IcuFormatter.ts +17 -0
  53. package/src/modules/index.ts +1 -0
  54. package/src/services/ApiHttpService.ts +85 -0
  55. package/src/services/CoreService.test.ts +142 -0
  56. package/src/services/CoreService.ts +76 -0
  57. package/src/services/DependencyService.test.ts +51 -0
  58. package/src/services/DependencyService.ts +116 -0
  59. package/src/services/ElementRegistrar.test.ts +131 -0
  60. package/src/services/ElementRegistrar.ts +108 -0
  61. package/src/services/EventEmitter.ts +52 -0
  62. package/src/services/EventService.ts +14 -0
  63. package/src/services/ModuleService.ts +14 -0
  64. package/src/services/ScreenshotService.ts +31 -0
  65. package/src/services/Subscription.ts +7 -0
  66. package/src/services/TextService.test.ts +88 -0
  67. package/src/services/TextService.ts +82 -0
  68. package/src/services/TranslationService.test.ts +358 -0
  69. package/src/services/TranslationService.ts +417 -0
  70. package/src/services/__mocks__/CoreService.ts +17 -0
  71. package/src/toolsManager/Messages.test.ts +79 -0
  72. package/src/toolsManager/Messages.ts +60 -0
  73. package/src/toolsManager/PluginManager.test.ts +108 -0
  74. package/src/toolsManager/PluginManager.ts +129 -0
  75. package/src/types/DTOs.ts +25 -0
  76. package/src/types/apiSchema.generated.ts +6208 -0
  77. package/src/types.ts +146 -0
  78. package/src/wrappers/AbstractWrapper.ts +14 -0
  79. package/src/wrappers/NodeHandler.ts +143 -0
  80. package/src/wrappers/WrappedHandler.ts +28 -0
  81. package/src/wrappers/invisible/AttributeHandler.ts +23 -0
  82. package/src/wrappers/invisible/Coder.ts +65 -0
  83. package/src/wrappers/invisible/ContentHandler.ts +15 -0
  84. package/src/wrappers/invisible/CoreHandler.ts +17 -0
  85. package/src/wrappers/invisible/InvisibleWrapper.ts +59 -0
  86. package/src/wrappers/invisible/ValueMemory.test.ts +25 -0
  87. package/src/wrappers/invisible/ValueMemory.ts +16 -0
  88. package/src/wrappers/text/AttributeHandler.test.ts +117 -0
  89. package/src/wrappers/text/AttributeHandler.ts +25 -0
  90. package/src/wrappers/text/Coder.test.ts +298 -0
  91. package/src/wrappers/text/Coder.ts +202 -0
  92. package/src/wrappers/text/ContentHandler.test.ts +185 -0
  93. package/src/wrappers/text/ContentHandler.ts +21 -0
  94. package/src/wrappers/text/CoreHandler.test.ts +106 -0
  95. package/src/wrappers/text/CoreHandler.ts +45 -0
  96. package/src/wrappers/text/TextWrapper.ts +69 -0
  97. package/dist/tolgee.esm.js.map +0 -1
@@ -0,0 +1,177 @@
1
+ jest.dontMock('./TranslationHighlighter');
2
+ jest.dontMock('../services/DependencyService');
3
+
4
+ import { TranslationHighlighter } from './TranslationHighlighter';
5
+ import classMock from '@testFixtures/classMock';
6
+ import { getMockedInstance } from '@testFixtures/mocked';
7
+ import { Properties } from '../Properties';
8
+ import { createElement } from '@testFixtures/createElement';
9
+ import { DependencyService } from '../services/DependencyService';
10
+
11
+ describe('TranslationHighlighter', () => {
12
+ let translationHighlighter: TranslationHighlighter;
13
+
14
+ beforeEach(async () => {
15
+ const dependencyService = new DependencyService();
16
+ dependencyService.init({});
17
+ translationHighlighter = dependencyService.translationHighlighter;
18
+ });
19
+
20
+ afterEach(async () => {
21
+ jest.clearAllMocks();
22
+ window['@tolgee/ui'] = undefined;
23
+ });
24
+
25
+ describe('passing UI', () => {
26
+ const checkIt = async () => {
27
+ const mockedElement = createElement(20, 20, true);
28
+ translationHighlighter.listen(mockedElement);
29
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
30
+ expect(rendererViewerMock).toBeCalledTimes(1);
31
+ };
32
+
33
+ test('Works when UI is provided using regular provider', async () => {
34
+ getMockedInstance(Properties).config.ui = getUiClassMock();
35
+ await checkIt();
36
+ });
37
+
38
+ test('Works when UI is provided using promise provider', async () => {
39
+ // @ts-ignore
40
+ getMockedInstance(Properties).config.ui = new Promise((resolve) =>
41
+ resolve(getUiClassMock())
42
+ );
43
+ await checkIt();
44
+ });
45
+
46
+ test('works when UI is provided using window provider', async () => {
47
+ getMockedInstance(Properties).config.ui = undefined;
48
+ window['@tolgee/ui'] = {
49
+ UI: getUiClassMock(),
50
+ };
51
+
52
+ await checkIt();
53
+ });
54
+
55
+ test('works when UI is provided using window constructor', async () => {
56
+ getMockedInstance(Properties).config.ui = undefined;
57
+ window['@tolgee/ui'] = getUiClassMock();
58
+ await checkIt();
59
+ });
60
+ });
61
+
62
+ describe('key rendering', () => {
63
+ test('will open renderer key context menu when multiple nodes', async () => {
64
+ await testNodeCounts(2, 1);
65
+ });
66
+
67
+ test('will open renderer key context menu when multiple keys', async () => {
68
+ await testNodeCounts(1, 10);
69
+ });
70
+
71
+ test('will open translation dialog when single key', async () => {
72
+ const mockedElement = createElement(1, 1, true);
73
+ translationHighlighter.listen(mockedElement);
74
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
75
+
76
+ expect(rendererViewerMock).toBeCalledTimes(1);
77
+ expect(rendererViewerMock).toBeCalledWith('key', 'default value');
78
+ });
79
+
80
+ test('will open translation dialog when single key multiplied', async () => {
81
+ const mockedElement = createElement(20, 20, true);
82
+ translationHighlighter.listen(mockedElement);
83
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
84
+
85
+ expect(rendererViewerMock).toBeCalledTimes(1);
86
+ expect(rendererViewerMock).toBeCalledWith('key', 'default value');
87
+ });
88
+ });
89
+
90
+ describe('warnings & errors', () => {
91
+ test('will print error on no key', async () => {
92
+ // eslint-disable-next-line no-console
93
+ console.error = jest.fn();
94
+ rendererGetKeyMock = jest.fn(async (): Promise<string> => {
95
+ return;
96
+ });
97
+
98
+ rendererViewerMock = jest.fn();
99
+
100
+ getMockedInstance(Properties).config.ui = classMock<any>(
101
+ () => ({
102
+ getKey: rendererGetKeyMock,
103
+ }),
104
+ function () {
105
+ return {};
106
+ } as any
107
+ );
108
+
109
+ const mockedElement = createElement(0, 0);
110
+ translationHighlighter.listen(mockedElement);
111
+
112
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
113
+
114
+ // eslint-disable-next-line no-console
115
+ expect(console.error).toBeCalledTimes(1);
116
+ });
117
+
118
+ test('will print warning when UI not provided', async () => {
119
+ // eslint-disable-next-line no-console
120
+ console.warn = jest.fn();
121
+
122
+ getMockedInstance(Properties).config.ui = null;
123
+
124
+ const mockedElement = createElement(1, 1);
125
+ translationHighlighter.listen(mockedElement);
126
+
127
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
128
+
129
+ // eslint-disable-next-line no-console
130
+ expect(console.warn).toBeCalledTimes(1);
131
+ });
132
+ });
133
+
134
+ let rendererGetKeyMock: (...args) => Promise<string>;
135
+ let rendererViewerMock: (...args) => void;
136
+
137
+ beforeEach(() => {
138
+ rendererGetKeyMock = jest.fn(async (): Promise<string> => {
139
+ return 'test';
140
+ });
141
+
142
+ rendererViewerMock = jest.fn();
143
+ getMockedInstance(Properties).config.ui = getUiClassMock();
144
+ });
145
+
146
+ const openEvent = new MouseEvent('click');
147
+
148
+ const testNodeCounts = async (nodeCount, keyCount) => {
149
+ const mockedElement = createElement(nodeCount, keyCount);
150
+ translationHighlighter.listen(mockedElement);
151
+ await translationHighlighter.translationEdit(openEvent, mockedElement);
152
+ expect(rendererGetKeyMock).toBeCalledTimes(1);
153
+
154
+ const keySet = new Set();
155
+ for (let i = 0; i < nodeCount * keyCount; i++) {
156
+ keySet.add(`key ${i}`);
157
+ }
158
+
159
+ expect(rendererGetKeyMock).toBeCalledTimes(1);
160
+ expect(rendererGetKeyMock).toBeCalledWith({ keys: keySet, openEvent });
161
+ expect(rendererGetKeyMock).not.toBeCalledWith({
162
+ keys: new Set(['key 0']),
163
+ openEvent,
164
+ });
165
+ };
166
+
167
+ const getUiClassMock = () =>
168
+ classMock<any>(
169
+ () => ({
170
+ getKey: rendererGetKeyMock,
171
+ renderViewer: rendererViewerMock,
172
+ }),
173
+ function () {
174
+ return {};
175
+ } as any
176
+ );
177
+ });
@@ -0,0 +1,113 @@
1
+ import { ElementWithMeta } from '../types';
2
+ import { PluginManager } from '../toolsManager/PluginManager';
3
+ import { DependencyService } from '../services/DependencyService';
4
+
5
+ type KeyWithDefault = { key: string; defaultValue?: string };
6
+
7
+ export class TranslationHighlighter {
8
+ public pluginManager: PluginManager;
9
+ private _renderer: any;
10
+
11
+ constructor(private dependencies: DependencyService) {}
12
+
13
+ private static getKeyOptions(node: ElementWithMeta): KeyWithDefault[] {
14
+ const nodes = Array.from(node._tolgee.nodes);
15
+ return nodes.reduce(
16
+ (acc, curr) => [
17
+ ...acc,
18
+ ...curr._tolgee.keys.map((k) => ({
19
+ key: k.key,
20
+ defaultValue: k.defaultValue,
21
+ })),
22
+ ],
23
+ []
24
+ );
25
+ }
26
+
27
+ listen(element: ElementWithMeta & ElementCSSInlineStyle) {
28
+ this.dependencies.highlightFunctionInitializer.initFunctions(element);
29
+ }
30
+
31
+ private async getRenderer() {
32
+ if (this._renderer === undefined) {
33
+ const possibleProviders = [
34
+ this.dependencies.properties.config.ui,
35
+ window['@tolgee/ui'],
36
+ ];
37
+ for (const possiblePromise of possibleProviders) {
38
+ // if dynamic import is used
39
+ const possibleObject =
40
+ possiblePromise instanceof Promise
41
+ ? await possiblePromise
42
+ : possiblePromise;
43
+
44
+ // extract .UI property
45
+ const possibleProvider =
46
+ typeof possibleObject === 'object'
47
+ ? possibleObject?.UI
48
+ : possibleObject;
49
+
50
+ if (typeof possibleProvider === 'function') {
51
+ this._renderer = new possibleProvider(this.dependencies);
52
+ break;
53
+ }
54
+ }
55
+ if (this._renderer === undefined) {
56
+ // eslint-disable-next-line no-console
57
+ console.warn(
58
+ 'Tolgee UI is not provided. To translate interactively provide tolgee ui constructor to "ui" configuration property. ' +
59
+ 'To disable highlighting use production mode.'
60
+ );
61
+ }
62
+ }
63
+ return this._renderer;
64
+ }
65
+
66
+ private async getKeyAndDefault(
67
+ mouseEvent: MouseEvent,
68
+ element: ElementWithMeta
69
+ ): Promise<KeyWithDefault> {
70
+ if (element._tolgee.wrappedWithElementOnlyKey) {
71
+ return {
72
+ key: element._tolgee.wrappedWithElementOnlyKey,
73
+ defaultValue: element._tolgee.wrappedWithElementOnlyDefaultHtml,
74
+ };
75
+ }
76
+ const keysWithDefaults = TranslationHighlighter.getKeyOptions(element);
77
+
78
+ // create Set to remove duplicated key values
79
+ const keySet = new Set(
80
+ keysWithDefaults.map((keyWithDefault) => keyWithDefault.key)
81
+ );
82
+ if (keySet.size > 1) {
83
+ const renderer = await this.getRenderer();
84
+ // this opens the popover where user chooses the key
85
+ const selectedKey = await renderer.getKey({
86
+ keys: keySet,
87
+ openEvent: mouseEvent,
88
+ });
89
+ // get the key with default
90
+ const found = keysWithDefaults.find((kwd) => kwd.key === selectedKey);
91
+ if (found) {
92
+ return found;
93
+ }
94
+ }
95
+ if (keySet.size === 1) {
96
+ return keysWithDefaults[0];
97
+ }
98
+ // eslint-disable-next-line no-console
99
+ console.error('No key to translate. This seems like a bug in tolgee.');
100
+ }
101
+
102
+ public translationEdit = async (e: MouseEvent, element: ElementWithMeta) => {
103
+ const renderer = await this.getRenderer();
104
+ if (typeof renderer === 'object') {
105
+ const key = await this.getKeyAndDefault(e, element);
106
+ if (key) {
107
+ renderer.renderViewer(key.key, key.defaultValue);
108
+ return;
109
+ }
110
+ return;
111
+ }
112
+ };
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { Tolgee } from './Tolgee';
2
+ export { TolgeeConfig } from './TolgeeConfig';
3
+ export { ModifierKey } from './Constants/ModifierKey';
4
+ export { TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE } from './Constants/Global';
5
+ export * from './modules';
6
+ export * from './types/DTOs';
7
+ export * from './types';
8
+ export * from './Constants/Global';
9
+ export * from './helpers/NodeHelper';
10
+ export * from './helpers/TextHelper';
@@ -0,0 +1,2 @@
1
+ export { NodeHelper } from './helpers/NodeHelper';
2
+ export { TextHelper } from './helpers/TextHelper';
@@ -0,0 +1,17 @@
1
+ import IntlMessageFormat from 'intl-messageformat';
2
+ import { TolgeeModule } from '../types';
3
+ export const IcuFormatter: TolgeeModule = class {
4
+ static type = 'formatter' as const;
5
+
6
+ cache = new Map<string, IntlMessageFormat>();
7
+
8
+ format({ translation, language, params }) {
9
+ const ignoreTag = !Object.values(params).find(
10
+ (p) => typeof p === 'function'
11
+ );
12
+
13
+ return new IntlMessageFormat(translation, language, undefined, {
14
+ ignoreTag,
15
+ }).format(params) as string;
16
+ }
17
+ };
@@ -0,0 +1 @@
1
+ export { IcuFormatter } from './IcuFormatter';
@@ -0,0 +1,85 @@
1
+ import { Properties } from '../Properties';
2
+ import { ApiHttpError } from '../Errors/ApiHttpError';
3
+ import { ArgumentTypes } from '../helpers/commonTypes';
4
+
5
+ type FetchArgumentTypes = ArgumentTypes<typeof fetch>;
6
+
7
+ type Tail<T extends any[]> = ((...args: T) => any) extends (
8
+ _: infer First,
9
+ ...rest: infer Rest
10
+ ) => any
11
+ ? T extends any[]
12
+ ? Rest
13
+ : ReadonlyArray<Rest[number]>
14
+ : [];
15
+
16
+ export class ApiHttpService {
17
+ constructor(private properties: Properties) {}
18
+
19
+ private static async handleErrors(response: Response) {
20
+ if (response.status >= 400) {
21
+ const error = new ApiHttpError(response);
22
+ try {
23
+ const data = await response.json();
24
+ error.code = data.code;
25
+ } catch (e) {
26
+ // eslint-disable-next-line no-console
27
+ console.warn('Tolgee server responded with invalid status code.');
28
+ }
29
+ throw error;
30
+ }
31
+ return response;
32
+ }
33
+
34
+ async fetch(...args: ArgumentTypes<typeof fetch>) {
35
+ if (typeof args[0] === 'object') {
36
+ return fetch({ ...args[0], url: this.getUrl((args[0] as any).url) }).then(
37
+ (r) => ApiHttpService.handleErrors(r)
38
+ );
39
+ }
40
+ const [url, ...rest] = args;
41
+ return fetch(this.getUrl(url), ...rest).then((r) =>
42
+ ApiHttpService.handleErrors(r)
43
+ );
44
+ }
45
+
46
+ async fetchJson(...args: ArgumentTypes<typeof fetch>) {
47
+ return this.fetch(...args).then((res) => {
48
+ return res.json();
49
+ });
50
+ }
51
+
52
+ async post(
53
+ url,
54
+ body,
55
+ init: FetchArgumentTypes[1] = {},
56
+ ...rest: Tail<Tail<FetchArgumentTypes>>
57
+ ) {
58
+ return this.fetch(
59
+ url,
60
+ {
61
+ body: JSON.stringify(body),
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ },
66
+ ...init,
67
+ },
68
+ ...rest
69
+ );
70
+ }
71
+
72
+ async postJson(
73
+ url,
74
+ body,
75
+ init: FetchArgumentTypes[1] = {},
76
+ ...rest: Tail<Tail<FetchArgumentTypes>>
77
+ ) {
78
+ return this.post(url, body, init, ...rest).then((res) => res.json());
79
+ }
80
+
81
+ private getUrl(path: string) {
82
+ const querySeparator = path.indexOf('?') < 0 ? '?' : '&';
83
+ return `${this.properties.config.apiUrl}/${path}${querySeparator}ak=${this.properties.config.apiKey}`;
84
+ }
85
+ }
@@ -0,0 +1,142 @@
1
+ jest.dontMock('./CoreService');
2
+ jest.dontMock('./DependencyService');
3
+
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { CoreService } from './CoreService';
6
+ import { getMockedInstance } from '@testFixtures/mocked';
7
+ import { ApiHttpService } from './ApiHttpService';
8
+ import { mocked } from 'ts-jest/utils';
9
+ import { Properties } from '../Properties';
10
+ import { Scope } from '../types';
11
+ import { DependencyService } from './DependencyService';
12
+
13
+ describe('CoreService', () => {
14
+ let coreService: CoreService;
15
+ let mockedFetchJson;
16
+
17
+ beforeEach(() => {
18
+ coreService = new DependencyService().coreService;
19
+ getMockedInstance(Properties).preferredLanguages = new Set<string>();
20
+ getMockedInstance(Properties).config = {
21
+ inputPrefix: '{{',
22
+ inputSuffix: '}}',
23
+ restrictedElements: [],
24
+ tagAttributes: {
25
+ '*': ['aria-label'],
26
+ },
27
+ };
28
+ mockedFetchJson = mocked(getMockedInstance(ApiHttpService).fetchJson);
29
+ });
30
+
31
+ afterEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ test('can be created', () => {
36
+ expect(coreService).not.toBeNull();
37
+ });
38
+
39
+ describe('getLanguages', () => {
40
+ const dummyLanguages = ['dummyLang1', 'dummyLang2'];
41
+
42
+ beforeEach(() => {
43
+ mockedFetchJson.mockImplementation(async () => {
44
+ return {
45
+ _embedded: {
46
+ languages: dummyLanguages.map((l) => ({
47
+ tag: l,
48
+ })),
49
+ },
50
+ };
51
+ });
52
+ });
53
+
54
+ test('will return languages returned from api http service', async () => {
55
+ expect(await coreService.getLanguages()).toEqual(new Set(dummyLanguages));
56
+ expect(mockedFetchJson).toBeCalledTimes(1);
57
+ expect(mockedFetchJson).toBeCalledWith(`v2/projects/languages?size=1000`);
58
+ });
59
+
60
+ test('sets preferred languages of properties', async () => {
61
+ getMockedInstance(Properties).preferredLanguages = new Set([
62
+ dummyLanguages[0],
63
+ ]);
64
+ await coreService.getLanguages();
65
+ expect(getMockedInstance(Properties).preferredLanguages).toEqual(
66
+ new Set([dummyLanguages[0]])
67
+ );
68
+ });
69
+ });
70
+
71
+ describe('getScopes', () => {
72
+ // eslint-disable-next-line no-console
73
+ console.error = jest.fn();
74
+
75
+ test('will switch to production mode on error', async () => {
76
+ mocked(mockedFetchJson).mockImplementation(async () => {
77
+ throw new Error();
78
+ });
79
+ await coreService.getApiKeyDetails();
80
+ expect(getMockedInstance(Properties).mode).toEqual('production');
81
+ // eslint-disable-next-line no-console
82
+ expect(console.error).toBeCalledTimes(2);
83
+ });
84
+
85
+ test('will return value from http service', async () => {
86
+ const mockedReturn = {
87
+ scopes: ['translations.view', 'translations.edit'],
88
+ };
89
+ mocked(mockedFetchJson).mockImplementation(async () => mockedReturn);
90
+ expect(await coreService.getApiKeyDetails()).toEqual(mockedReturn);
91
+ });
92
+ });
93
+
94
+ describe('loadApiKeyDetails', () => {
95
+ beforeEach(() => {
96
+ const mockedReturn = {
97
+ scopes: ['translations.edit'],
98
+ projectId: 0,
99
+ };
100
+ mocked(mockedFetchJson).mockImplementation(async () => mockedReturn);
101
+ });
102
+
103
+ test('will set properties.scopes on run in development mode', async () => {
104
+ const propertiesMock = getMockedInstance(Properties);
105
+ propertiesMock.mode = 'development';
106
+ await coreService.loadApiKeyDetails();
107
+ expect(propertiesMock.scopes).toContain('translations.edit' as Scope);
108
+ expect(propertiesMock.scopes).not.toContain('translations.view' as Scope);
109
+ });
110
+
111
+ test('will set properties.projectId on run in development mode', async () => {
112
+ const propertiesMock = getMockedInstance(Properties);
113
+ propertiesMock.mode = 'development';
114
+ await coreService.loadApiKeyDetails();
115
+ expect(propertiesMock.projectId).toEqual(0);
116
+ });
117
+ });
118
+
119
+ describe('Authorization', () => {
120
+ test('will return proper value on isAuthorizedTo', () => {
121
+ getMockedInstance(Properties).scopes = [
122
+ 'translations.edit',
123
+ 'translations.view',
124
+ ] as Scope[];
125
+ expect(coreService.isAuthorizedTo('keys.edit')).toBeFalsy();
126
+ expect(coreService.isAuthorizedTo('translations.view')).toBeTruthy();
127
+ });
128
+
129
+ test('will return proper on checkScopes', () => {
130
+ getMockedInstance(Properties).scopes = [
131
+ 'translations.edit',
132
+ 'translations.view',
133
+ ] as Scope[];
134
+ expect(jest.fn(() => coreService.checkScope('keys.edit'))).toThrowError();
135
+ const checkMock = jest.fn(() =>
136
+ coreService.checkScope('translations.view')
137
+ );
138
+ checkMock();
139
+ expect(checkMock).toReturn();
140
+ });
141
+ });
142
+ });
@@ -0,0 +1,76 @@
1
+ import { Properties } from '../Properties';
2
+ import { ApiHttpService } from './ApiHttpService';
3
+ import { Scope } from '../types';
4
+ import { LanguageModel, PagedModelLanguageModel } from '../types/DTOs';
5
+ import { components } from '../types/apiSchema.generated';
6
+
7
+ export type ApiKeyWithLanguagesModel =
8
+ components['schemas']['ApiKeyWithLanguagesModel'];
9
+
10
+ export class CoreService {
11
+ private languagePromise: Promise<PagedModelLanguageModel>;
12
+
13
+ constructor(
14
+ private properties: Properties,
15
+ private apiHttpService: ApiHttpService
16
+ ) {}
17
+
18
+ async getLanguages(): Promise<Set<string>> {
19
+ if (!(this.languagePromise instanceof Promise)) {
20
+ this.languagePromise = this.apiHttpService.fetchJson(
21
+ `v2/projects/languages?size=1000`
22
+ );
23
+ }
24
+
25
+ const languages = new Set(
26
+ (await this.languagePromise)._embedded.languages.map((l) => l.tag)
27
+ );
28
+ this.properties.preferredLanguages = new Set<string>(
29
+ Array.from(this.properties.preferredLanguages).filter((l) =>
30
+ languages.has(l)
31
+ )
32
+ );
33
+ return languages;
34
+ }
35
+
36
+ async getLanguagesFull(): Promise<LanguageModel[]> {
37
+ this.getLanguages();
38
+ const languages = await this.languagePromise;
39
+ return languages._embedded.languages;
40
+ }
41
+
42
+ async getApiKeyDetails(): Promise<ApiKeyWithLanguagesModel> {
43
+ try {
44
+ return await this.apiHttpService.fetchJson(`v2/api-keys/current`);
45
+ } catch (e) {
46
+ // eslint-disable-next-line no-console
47
+ console.error(e);
48
+ // eslint-disable-next-line no-console
49
+ console.error(
50
+ 'Error getting scopes. Trying to switch to production mode!'
51
+ );
52
+ this.properties.mode = 'production';
53
+ }
54
+ }
55
+
56
+ isAuthorizedTo(scope: Scope) {
57
+ return this.properties.scopes.indexOf(scope) > -1;
58
+ }
59
+
60
+ checkScope(scope: Scope) {
61
+ if (!this.isAuthorizedTo(scope)) {
62
+ throw new Error(
63
+ "Api key not permitted to do this, please add 'translations.view' scope."
64
+ );
65
+ }
66
+ }
67
+
68
+ async loadApiKeyDetails() {
69
+ if (this.properties.scopes === undefined) {
70
+ const details = await this.getApiKeyDetails();
71
+ this.properties.scopes = details.scopes as Scope[];
72
+ this.properties.projectId = details.projectId;
73
+ this.properties.permittedLanguageIds = details.permittedLanguageIds;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,51 @@
1
+ jest.dontMock('./DependencyService');
2
+ jest.dontMock('../TolgeeConfig');
3
+
4
+ import { getMockedInstance } from '@testFixtures/mocked';
5
+ import { mocked } from 'ts-jest/utils';
6
+ import { InvisibleWrapper } from '../wrappers/invisible/InvisibleWrapper';
7
+ import { TextWrapper } from '../wrappers/text/TextWrapper';
8
+ import { DependencyService } from './DependencyService';
9
+
10
+ describe('DependecyStore', () => {
11
+ let dependecyService: DependencyService;
12
+
13
+ const config = {
14
+ apiKey: 'yep',
15
+ };
16
+
17
+ beforeEach(async () => {
18
+ dependecyService = new DependencyService();
19
+ });
20
+
21
+ afterEach(async () => {
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ it('inits translation service', () => {
26
+ dependecyService.init(config);
27
+ dependecyService.run();
28
+ expect(
29
+ mocked(dependecyService.translationService).initStatic
30
+ ).toBeCalledTimes(1);
31
+ });
32
+
33
+ it('sets config to properties', () => {
34
+ dependecyService.init(config);
35
+ expect(dependecyService.properties.config.apiKey).toEqual(config.apiKey);
36
+ });
37
+
38
+ it('inits text wrapper', () => {
39
+ dependecyService.init(config);
40
+ dependecyService.run();
41
+ expect(dependecyService.wrapper).toEqual(getMockedInstance(TextWrapper));
42
+ });
43
+
44
+ it('inits invisible wrapper', () => {
45
+ dependecyService.init({ ...config, wrapperMode: 'invisible' });
46
+ dependecyService.run();
47
+ expect(dependecyService.wrapper).toEqual(
48
+ getMockedInstance(InvisibleWrapper)
49
+ );
50
+ });
51
+ });