Skip to content

Commit 40ddbe9

Browse files
authored
feat: Add method to change translation for screenshot taking (#496)
1 parent b6e5040 commit 40ddbe9

10 files changed

Lines changed: 306 additions & 71 deletions

File tree

packages/core/src/helpers/sleep.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const sleep = (ms: number) =>
2+
new Promise((resolve) => setTimeout(resolve, ms));

packages/core/src/services/DependencyStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export class DependencyStore {
9292
this.messages,
9393
this.properties,
9494
this.eventService,
95-
this.elementRegistrar
95+
this.elementRegistrar,
96+
this.translationService
9697
);
9798

9899
constructor(config: TolgeeConfig = new TolgeeConfig()) {

packages/core/src/services/TranslationService.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ global.fetch = jest.fn(async (url: string) => {
4848
describe('TranslationService', () => {
4949
let translationService: TranslationService;
5050
const languageLoadedEmitMock = jest.fn();
51+
const translationChangedEmitMock = jest.fn();
5152

5253
beforeEach(async () => {
5354
translationService = new DependencyStore().translationService;
5455
(getMockedInstance(EventService) as any).LANGUAGE_LOADED = {
5556
emit: languageLoadedEmitMock,
5657
};
58+
(getMockedInstance(EventService) as any).TRANSLATION_CHANGED = {
59+
emit: translationChangedEmitMock,
60+
};
5761
getMockedInstance(ApiHttpService).fetchJson = jest.fn(
5862
async () => mockedTranslations
5963
);
@@ -198,6 +202,11 @@ describe('TranslationService', () => {
198202
expect(getMockedInstance(CoreService).checkScope).toBeCalledTimes(1);
199203
});
200204

205+
test('emits translation changed event', async () => {
206+
expect(translationChangedEmitMock).toBeCalledTimes(1);
207+
expect(translationChangedEmitMock).toBeCalledWith(dummyTranslationData);
208+
});
209+
201210
test('will update the data', async () => {
202211
await translationService.loadTranslations('en');
203212
await translationService.setTranslations(dummyTranslationData);

packages/core/src/services/TranslationService.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,60 @@ export class TranslationService {
122122
data[translationData.key] = translationData.translations[lang];
123123
}
124124
});
125+
126+
await (
127+
this.eventService.TRANSLATION_CHANGED as EventEmitterImpl<TranslationData>
128+
).emit(translationData);
129+
125130
return result;
126131
}
127132

133+
/**
134+
* Change translations of some keys to some value temporarily.
135+
* For screenshot taking with provided values, before actually saving
136+
* the values
137+
*
138+
* @return Returns callback changing affected translations back
139+
*/
140+
async changeTranslations({
141+
key,
142+
translations,
143+
}: TranslationData): Promise<() => void> {
144+
const old: Record<string, string> = {};
145+
146+
Object.entries(translations).forEach(([language, value]) => {
147+
const data = this.translationsCache.get(language);
148+
if (data) {
149+
old[language] = value;
150+
data[key] = value;
151+
}
152+
});
153+
154+
await (
155+
this.eventService.TRANSLATION_CHANGED as EventEmitterImpl<TranslationData>
156+
).emit({
157+
key,
158+
translations,
159+
});
160+
161+
// callback to revert the operation
162+
return async () => {
163+
Object.entries(old).forEach(([language, value]) => {
164+
const data = this.translationsCache.get(language);
165+
if (data) {
166+
data[key] = value;
167+
}
168+
});
169+
await (
170+
this.eventService
171+
.TRANSLATION_CHANGED as EventEmitterImpl<TranslationData>
172+
).emit({
173+
key,
174+
translations: old,
175+
});
176+
};
177+
}
178+
128179
async uploadScreenshot(key, data) {
129180
this.coreService.checkScope('translations.edit');
130181

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
jest.dontMock('./Messages');
2+
jest.dontMock('../services/DependencyStore');
3+
4+
import { Messages } from './Messages';
5+
import { DependencyStore } from '../services/DependencyStore';
6+
7+
describe('Messages', () => {
8+
let messages: Messages;
9+
let windowAddEventListenerSpy;
10+
let windowRemoveEventListenerSpy;
11+
12+
beforeEach(async () => {
13+
messages = new DependencyStore().messages;
14+
windowAddEventListenerSpy = jest.spyOn(window, 'addEventListener');
15+
windowRemoveEventListenerSpy = jest.spyOn(window, 'removeEventListener');
16+
messages.startListening();
17+
});
18+
19+
afterEach(() => {
20+
jest.clearAllMocks();
21+
});
22+
23+
test('Can be created', () => {
24+
expect(messages).toBeInstanceOf(Messages);
25+
});
26+
27+
describe('starListening method', () => {
28+
test('adds event listener to window', () => {
29+
expect(windowAddEventListenerSpy).toHaveBeenCalledTimes(1);
30+
});
31+
});
32+
33+
describe('listening', () => {
34+
let resolve;
35+
const promise = new Promise((r) => (resolve = r));
36+
const callbackMock = jest.fn(() => {
37+
resolve();
38+
});
39+
const data = {};
40+
let removeListener;
41+
const sendMessage = () =>
42+
window.dispatchEvent(
43+
new MessageEvent('message', {
44+
data: { type: 'TEST_EVENT', data },
45+
source: window,
46+
origin: window.origin,
47+
})
48+
);
49+
50+
beforeEach(async () => {
51+
removeListener = messages.listen('TEST_EVENT', callbackMock);
52+
sendMessage();
53+
await promise;
54+
});
55+
56+
test('listens to event', async () => {
57+
expect(callbackMock).toHaveBeenCalledTimes(1);
58+
expect(callbackMock).toHaveBeenCalledWith(data);
59+
});
60+
61+
test('returns callback to remove listener', () => {
62+
expect((messages as any).listeners).toHaveLength(1);
63+
removeListener();
64+
expect((messages as any).listeners).toHaveLength(0);
65+
});
66+
});
67+
68+
describe('stopListening method', () => {
69+
test('stopsListening', () => {
70+
messages.stopListening();
71+
expect(windowRemoveEventListenerSpy).toHaveBeenCalledTimes(1);
72+
expect(windowRemoveEventListenerSpy).toHaveBeenCalledWith(
73+
windowAddEventListenerSpy.mock.calls[0][0],
74+
windowAddEventListenerSpy.mock.calls[0][1],
75+
windowAddEventListenerSpy.mock.calls[0][2]
76+
);
77+
});
78+
});
79+
});

packages/core/src/toolsManager/Messages.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ type Message = {
88
type: string;
99
};
1010

11-
type PgEvent = {
11+
type TolgeeEvent = {
1212
data: Message;
1313
} & MessageEvent;
1414

1515
export class Messages {
1616
private listeners: Listener[] = [];
17-
private listenersPopup: Listener[] = [];
1817
private _stopListening: () => void;
1918

2019
readonly startListening = () => {
21-
const receiveMessage = (event: PgEvent) => {
20+
const receiveMessage = (event: TolgeeEvent) => {
2221
if (event.source != window) {
2322
return;
2423
}
@@ -35,38 +34,27 @@ export class Messages {
3534
this._stopListening = () => {
3635
window.removeEventListener('message', receiveMessage, false);
3736
};
38-
39-
this.startPopupListening();
4037
};
4138

4239
public stopListening() {
4340
this._stopListening();
4441
}
4542

46-
readonly startPopupListening = () => {
47-
this.listen('POPUP_TO_LIB', (data: Message) => {
48-
this.listenersPopup.forEach((listener) => {
49-
if (data.type == listener.type) {
50-
listener.callback(data.data);
51-
}
52-
});
53-
});
54-
};
55-
56-
readonly listenPopup = (type: string, callback: (data) => void) => {
57-
this.listenersPopup.push({ type, callback });
58-
};
59-
6043
readonly listen = (type: string, callback: (data) => void) => {
61-
this.listeners.push({ type, callback });
44+
const listenerInfo = { type, callback };
45+
this.listeners.push(listenerInfo);
46+
// return callback to remove the listener
47+
return () => {
48+
this.listeners.splice(this.listeners.indexOf(listenerInfo), 1);
49+
};
6250
};
6351

6452
readonly send = (type: string, data?: any) => {
6553
try {
6654
window.postMessage({ type, data }, window.origin);
6755
} catch (e) {
6856
// eslint-disable-next-line no-console
69-
console.warn('Can not send message.', e);
57+
console.warn('Cannot send message.', e);
7058
}
7159
};
7260
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
jest.dontMock('./PluginManager');
2+
jest.dontMock('../services/DependencyStore');
3+
4+
import { ElementWithMeta } from '../types';
5+
import { ElementRegistrar } from '../services/ElementRegistrar';
6+
import { getMockedInstance } from '@testFixtures/mocked';
7+
import { PluginManager } from './PluginManager';
8+
import { DependencyStore } from '../services/DependencyStore';
9+
import { TranslationService } from '../services/TranslationService';
10+
import { Messages } from './Messages';
11+
12+
describe('PluginManager', () => {
13+
let pluginManager: PluginManager;
14+
15+
beforeEach(async () => {
16+
pluginManager = new DependencyStore().pluginManager;
17+
});
18+
19+
afterEach(() => {
20+
jest.clearAllMocks();
21+
});
22+
23+
test('Can be created', () => {
24+
expect(pluginManager).toBeInstanceOf(PluginManager);
25+
});
26+
27+
describe('take screenshot methods', () => {
28+
const data = {
29+
key: 'test_key',
30+
translations: { en: 'English!' },
31+
};
32+
33+
let resolve;
34+
const highlightMock = jest.fn();
35+
const unHighlightMock = jest.fn();
36+
let listenCallback;
37+
const cancelMock = jest.fn();
38+
const revertMock = jest.fn();
39+
40+
beforeEach(async () => {
41+
getMockedInstance(TranslationService).changeTranslations = jest.fn(
42+
() =>
43+
new Promise((r) => {
44+
resolve = r;
45+
})
46+
);
47+
48+
getMockedInstance(ElementRegistrar).findAllByKey = jest.fn(() => {
49+
const element = document.createElement(
50+
'span'
51+
) as any as ElementWithMeta;
52+
element._tolgee = {
53+
highlight: highlightMock,
54+
unhighlight: unHighlightMock,
55+
} as any;
56+
57+
return [element];
58+
});
59+
60+
(getMockedInstance(Messages) as any).send = jest.fn();
61+
62+
(getMockedInstance(Messages) as any).listen = jest.fn((_, callback) => {
63+
listenCallback = callback;
64+
return cancelMock;
65+
});
66+
67+
pluginManager.takeScreenshot(data);
68+
resolve(revertMock);
69+
});
70+
71+
test('calls change translation', () => {
72+
expect(
73+
getMockedInstance(TranslationService).changeTranslations
74+
).toHaveBeenCalledWith(data);
75+
});
76+
77+
test('sends the message to take screenshots', () => {
78+
expect((getMockedInstance(Messages) as any).send).toHaveBeenCalledTimes(
79+
1
80+
);
81+
});
82+
83+
test('highlights all', () => {
84+
expect(
85+
getMockedInstance(ElementRegistrar).findAllByKey
86+
).toHaveBeenCalledWith(data.key);
87+
expect(
88+
getMockedInstance(ElementRegistrar).findAllByKey
89+
).toHaveBeenCalledTimes(1);
90+
expect(highlightMock).toHaveBeenCalledTimes(1);
91+
});
92+
93+
test('unhighlights all after', () => {
94+
listenCallback();
95+
expect(unHighlightMock).toHaveBeenCalledTimes(1);
96+
});
97+
98+
test('reverts the translation change', () => {
99+
listenCallback();
100+
expect(revertMock).toHaveBeenCalledTimes(1);
101+
});
102+
103+
test('cancels the listening', () => {
104+
listenCallback();
105+
expect(cancelMock).toHaveBeenCalledTimes(1);
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)