@tma.js/sdk 1.2.1 → 1.4.0
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.
- package/dist/dts/index.d.ts +1 -1
- package/dist/dts/init/creators/createViewport.d.ts +2 -9
- package/dist/dts/init/init.d.ts +2 -0
- package/dist/dts/init/types.d.ts +7 -4
- package/dist/dts/launch-params/index.d.ts +1 -0
- package/dist/dts/launch-params/retrieveFromUrl.d.ts +6 -0
- package/dist/dts/launch-params/types.d.ts +12 -8
- package/dist/dts/types/platform.d.ts +1 -1
- package/dist/dts/viewport/index.d.ts +1 -0
- package/dist/dts/viewport/isStableViewportPlatform.d.ts +7 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.iife.js +1 -1
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +469 -467
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/globals.ts +39 -0
- package/src/back-button/__tests__/BackButton.ts +129 -0
- package/src/bridge/__tests__/request.ts +236 -0
- package/src/bridge/env/__tests__/hasExternalNotify.ts +15 -0
- package/src/bridge/env/__tests__/hasWebviewProxy.ts +15 -0
- package/src/bridge/env/__tests__/isIframe.ts +30 -0
- package/src/bridge/events/__tests__/createEmitter.ts +143 -0
- package/src/bridge/events/__tests__/off.ts +34 -0
- package/src/bridge/events/__tests__/on.ts +49 -0
- package/src/bridge/events/__tests__/onTelegramEvent.ts +51 -0
- package/src/bridge/events/__tests__/once.ts +64 -0
- package/src/bridge/events/__tests__/singletonEmitter.ts +22 -0
- package/src/bridge/events/__tests__/subscribe.ts +49 -0
- package/src/bridge/events/__tests__/unsubscribe.ts +34 -0
- package/src/bridge/events/parsers/__tests__/clipboardTextReceived.ts +21 -0
- package/src/bridge/events/parsers/__tests__/invoiceClosed.ts +12 -0
- package/src/bridge/events/parsers/__tests__/popupClosed.ts +10 -0
- package/src/bridge/events/parsers/__tests__/qrTextReceived.ts +9 -0
- package/src/bridge/events/parsers/__tests__/theme-changed.ts +42 -0
- package/src/bridge/events/parsers/__tests__/viewportChanged.ts +49 -0
- package/src/bridge/methods/__tests__/createPostEvent.ts +37 -0
- package/src/bridge/methods/__tests__/postEvent.ts +137 -0
- package/src/classnames/__tests__/classNames.ts +20 -0
- package/src/classnames/__tests__/mergeClassNames.ts +21 -0
- package/src/closing-behavior/__tests__/ClosingBehavior.ts +86 -0
- package/src/colors/__tests__/isColorDark.ts +12 -0
- package/src/colors/__tests__/isRGB.ts +13 -0
- package/src/colors/__tests__/isRGBShort.ts +13 -0
- package/src/colors/__tests__/toRGB.ts +23 -0
- package/src/event-emitter/__tests__/EventEmitter.ts +145 -0
- package/src/haptic-feedback/__tests__/HapticFeedback.ts +68 -0
- package/src/index.ts +1 -0
- package/src/init/creators/__tests__/createViewport.ts +96 -0
- package/src/init/creators/createViewport.ts +60 -81
- package/src/init/init.ts +13 -15
- package/src/init/types.ts +8 -4
- package/src/init-data/__tests__/InitData.ts +98 -0
- package/src/init-data/__tests__/chatParser.ts +102 -0
- package/src/init-data/__tests__/initDataParser.ts +136 -0
- package/src/init-data/__tests__/parseInitData.ts +136 -0
- package/src/init-data/__tests__/userParser.ts +96 -0
- package/src/launch-params/__tests__/retrieveFromUrl.ts +19 -0
- package/src/launch-params/index.ts +1 -0
- package/src/launch-params/launchParamsParser.ts +4 -0
- package/src/launch-params/retrieveFromLocation.ts +2 -2
- package/src/launch-params/retrieveFromPerformance.ts +2 -7
- package/src/launch-params/retrieveFromUrl.ts +19 -0
- package/src/launch-params/types.ts +13 -8
- package/src/logger/__tests__/Logger.ts +107 -0
- package/src/main-button/__tests__/MainButton.ts +346 -0
- package/src/mini-app/__tests__/MiniApp.ts +140 -0
- package/src/misc/__tests__/isRecord.ts +21 -0
- package/src/navigation/HashNavigator/__tests__/HashNavigator.ts +144 -0
- package/src/navigation/HashNavigator/__tests__/drop.ts +42 -0
- package/src/navigation/HashNavigator/__tests__/go.ts +9 -0
- package/src/parsing/__tests__/ArrayValueParser.ts +18 -0
- package/src/parsing/__tests__/toRecord.ts +10 -0
- package/src/parsing/parsers/__tests__/array.ts +39 -0
- package/src/parsing/parsers/__tests__/boolean.ts +31 -0
- package/src/parsing/parsers/__tests__/date.ts +25 -0
- package/src/parsing/parsers/__tests__/json.ts +80 -0
- package/src/parsing/parsers/__tests__/number.ts +23 -0
- package/src/parsing/parsers/__tests__/rgb.ts +22 -0
- package/src/parsing/parsers/__tests__/searchParams.ts +105 -0
- package/src/parsing/parsers/__tests__/string.ts +25 -0
- package/src/popup/__tests__/Popup.ts +130 -0
- package/src/popup/__tests__/preparePopupParams.ts +85 -0
- package/src/supports/__tests__/supports.ts +123 -0
- package/src/theme-params/__tests__/keys.ts +19 -0
- package/src/theme-params/__tests__/parseThemeParams.ts +29 -0
- package/src/theme-params/__tests__/serializeThemeParams.ts +29 -0
- package/src/theme-params/__tests__/themeParamsParser.ts +29 -0
- package/src/timeout/__tests__/isTimeoutError.ts +9 -0
- package/src/timeout/__tests__/withTimeout.ts +28 -0
- package/src/types/platform.ts +2 -2
- package/src/version/__tests__/compareVersions.ts +19 -0
- package/src/viewport/__tests__/isStableViewportPlatform.ts +15 -0
- package/src/viewport/__tests__/utils.ts +12 -0
- package/src/viewport/index.ts +1 -0
- package/src/viewport/isStableViewportPlatform.ts +10 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ClosingBehavior } from '../ClosingBehavior';
|
|
4
|
+
|
|
5
|
+
describe('disable', () => {
|
|
6
|
+
it('should call "web_app_setup_closing_behavior" method with "need_confirmation" equal to false', () => {
|
|
7
|
+
const postEvent = vi.fn();
|
|
8
|
+
const confirmation = new ClosingBehavior(true, postEvent);
|
|
9
|
+
|
|
10
|
+
expect(postEvent).toHaveBeenCalledTimes(0);
|
|
11
|
+
confirmation.disableConfirmation();
|
|
12
|
+
expect(postEvent).toHaveBeenCalledTimes(1);
|
|
13
|
+
expect(postEvent).toHaveBeenCalledWith('web_app_setup_closing_behavior', { need_confirmation: false });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should emit "isConfirmationNeededChanged" event with false value', () => {
|
|
17
|
+
const confirmation = new ClosingBehavior(true, vi.fn());
|
|
18
|
+
const listener = vi.fn();
|
|
19
|
+
|
|
20
|
+
confirmation.on('change:isConfirmationNeeded', listener);
|
|
21
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
22
|
+
confirmation.disableConfirmation();
|
|
23
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
24
|
+
expect(listener).toHaveBeenCalledWith(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('enable', () => {
|
|
29
|
+
it('should call "web_app_setup_closing_behavior" method with "need_confirmation" equal to true', () => {
|
|
30
|
+
const postEvent = vi.fn();
|
|
31
|
+
const confirmation = new ClosingBehavior(false, postEvent);
|
|
32
|
+
|
|
33
|
+
expect(postEvent).toHaveBeenCalledTimes(0);
|
|
34
|
+
confirmation.enableConfirmation();
|
|
35
|
+
expect(postEvent).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(postEvent).toHaveBeenCalledWith('web_app_setup_closing_behavior', { need_confirmation: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should emit "isConfirmationNeededChanged" event with true value', () => {
|
|
40
|
+
const confirmation = new ClosingBehavior(false, vi.fn());
|
|
41
|
+
const listener = vi.fn();
|
|
42
|
+
|
|
43
|
+
confirmation.on('change:isConfirmationNeeded', listener);
|
|
44
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
45
|
+
confirmation.enableConfirmation();
|
|
46
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
47
|
+
expect(listener).toHaveBeenCalledWith(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('on', () => {
|
|
52
|
+
describe('"isConfirmationNeededChanged" event', () => {
|
|
53
|
+
it('should add event listener to event', () => {
|
|
54
|
+
const listener = vi.fn();
|
|
55
|
+
const confirmation = new ClosingBehavior(false, vi.fn());
|
|
56
|
+
|
|
57
|
+
confirmation.on('change:isConfirmationNeeded', listener);
|
|
58
|
+
|
|
59
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
60
|
+
confirmation.enableConfirmation();
|
|
61
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('off', () => {
|
|
67
|
+
describe('"isConfirmationNeededChanged" event', () => {
|
|
68
|
+
it('should remove event listener from event', () => {
|
|
69
|
+
const listener = vi.fn();
|
|
70
|
+
const confirmation = new ClosingBehavior(false, vi.fn());
|
|
71
|
+
|
|
72
|
+
confirmation.on('change:isConfirmationNeeded', listener);
|
|
73
|
+
|
|
74
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
75
|
+
confirmation.enableConfirmation();
|
|
76
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
77
|
+
|
|
78
|
+
confirmation.off('change:isConfirmationNeeded', listener);
|
|
79
|
+
listener.mockClear();
|
|
80
|
+
|
|
81
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
82
|
+
confirmation.disableConfirmation();
|
|
83
|
+
expect(listener).toHaveBeenCalledTimes(0);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isColorDark } from '../isColorDark';
|
|
4
|
+
|
|
5
|
+
it('should return true in case, the value which is equal to Math.sqrt(0.299 * R * R + 0.587 * G * G + 0.114 * B * B) is less than 120 and false otherwise', () => {
|
|
6
|
+
expect(isColorDark('#17212b')).toBe(true);
|
|
7
|
+
expect(isColorDark('#f5f5f5')).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should throw an error in case, passed value has not convertable to RGB format', () => {
|
|
11
|
+
expect(() => isColorDark('abc')).toThrow();
|
|
12
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isRGB } from '../isRGB';
|
|
4
|
+
|
|
5
|
+
it('should return true for correct full RGB representation', () => {
|
|
6
|
+
expect(isRGB('#ffffff')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return false for any other value', () => {
|
|
10
|
+
['abc', '#ffff', '#fff', '#fffffg'].forEach((v) => {
|
|
11
|
+
expect(isRGB(v)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isRGBShort } from '../isRGBShort';
|
|
4
|
+
|
|
5
|
+
it('should return true for correct short RGB representation', () => {
|
|
6
|
+
expect(isRGBShort('#fff')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return false for any other value', () => {
|
|
10
|
+
['abc', '#ffff', '#ffffff', '#ggg'].forEach((v) => {
|
|
11
|
+
expect(isRGBShort(v)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { toRGB } from '../toRGB';
|
|
4
|
+
|
|
5
|
+
it('should return same value in case, full version of RGB is passed', () => {
|
|
6
|
+
expect(toRGB('#ffffff')).toBe('#ffffff');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return full RGB value in case, its short presentation is passed', () => {
|
|
10
|
+
expect(toRGB('#abc')).toBe('#aabbcc');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return RGB representation of rgb(*,*,*) pattern', () => {
|
|
14
|
+
expect(toRGB('rgb(6,56,11)')).toBe('#06380b');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should return RGB representation of rgba(*,*,*) pattern', () => {
|
|
18
|
+
expect(toRGB('rgba(6,56,11,22)')).toBe('#06380b');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should throw an error in other cases', () => {
|
|
22
|
+
expect(() => toRGB('abc')).toThrow();
|
|
23
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { EventEmitter } from '../EventEmitter';
|
|
4
|
+
|
|
5
|
+
interface EventsMap {
|
|
6
|
+
test: (a: number, b: boolean) => void;
|
|
7
|
+
hey: never;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let ee: EventEmitter<EventsMap>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
ee = new EventEmitter<EventsMap>();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('on', () => {
|
|
17
|
+
it('should emit bound listener with specified arguments', () => {
|
|
18
|
+
const listener = vi.fn();
|
|
19
|
+
ee.on('test', listener);
|
|
20
|
+
ee.emit('test', 1, true);
|
|
21
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
22
|
+
expect(listener).toBeCalledWith(1, true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should emit bound listener with specified arguments as many times as it was bound', () => {
|
|
26
|
+
const listener = vi.fn();
|
|
27
|
+
ee.on('test', listener);
|
|
28
|
+
ee.on('test', listener);
|
|
29
|
+
ee.emit('test', 1, true);
|
|
30
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
31
|
+
expect(listener).toHaveBeenNthCalledWith(1, 1, true);
|
|
32
|
+
expect(listener).toHaveBeenNthCalledWith(2, 1, true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should not emit bound listener in case, event name does not match', () => {
|
|
36
|
+
const listener = vi.fn();
|
|
37
|
+
ee.on('test', listener);
|
|
38
|
+
ee.emit('hey');
|
|
39
|
+
expect(listener).not.toBeCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should remove listener if returned function was called', () => {
|
|
43
|
+
const listener = vi.fn();
|
|
44
|
+
const off = ee.on('test', listener);
|
|
45
|
+
|
|
46
|
+
off();
|
|
47
|
+
ee.emit('test', 1, true);
|
|
48
|
+
expect(listener).not.toBeCalled();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('once', () => {
|
|
53
|
+
it('should emit bound listener with specified arguments only once', () => {
|
|
54
|
+
const listener = vi.fn();
|
|
55
|
+
ee.once('test', listener);
|
|
56
|
+
ee.emit('test', 1, true);
|
|
57
|
+
ee.emit('test', 1, true);
|
|
58
|
+
ee.emit('test', 1, true);
|
|
59
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
60
|
+
expect(listener).toBeCalledWith(1, true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should emit bound listener with specified arguments as many times as it was bound', () => {
|
|
64
|
+
const listener = vi.fn();
|
|
65
|
+
ee.once('test', listener);
|
|
66
|
+
ee.once('test', listener);
|
|
67
|
+
ee.emit('test', 1, true);
|
|
68
|
+
ee.emit('test', 1, true);
|
|
69
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
70
|
+
expect(listener).toHaveBeenNthCalledWith(1, 1, true);
|
|
71
|
+
expect(listener).toHaveBeenNthCalledWith(2, 1, true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should not emit bound listener in case, event name does not match', () => {
|
|
75
|
+
const listener = vi.fn();
|
|
76
|
+
ee.once('test', listener);
|
|
77
|
+
ee.emit('hey');
|
|
78
|
+
expect(listener).not.toBeCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should remove listener if returned function was called', () => {
|
|
82
|
+
const listener = vi.fn();
|
|
83
|
+
const off = ee.once('test', listener);
|
|
84
|
+
|
|
85
|
+
off();
|
|
86
|
+
ee.emit('test', 1, true);
|
|
87
|
+
expect(listener).not.toBeCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('off', () => {
|
|
92
|
+
it('should not emit bound listener in case, it was unbound', () => {
|
|
93
|
+
const listener = vi.fn();
|
|
94
|
+
ee.on('test', listener);
|
|
95
|
+
ee.off('test', listener);
|
|
96
|
+
ee.emit('test', 1, true);
|
|
97
|
+
expect(listener).not.toBeCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should not do anything in case, event has no listeners', () => {
|
|
101
|
+
expect(() => {
|
|
102
|
+
const listener = vi.fn();
|
|
103
|
+
ee.off('test', listener);
|
|
104
|
+
}).not.toThrow();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should remove event listener bound via "once" method', () => {
|
|
108
|
+
const listener = vi.fn();
|
|
109
|
+
ee.once('test', listener);
|
|
110
|
+
ee.off('test', listener);
|
|
111
|
+
ee.emit('test', 1, true);
|
|
112
|
+
expect(listener).not.toBeCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should not do anything if received not bound listener', () => {
|
|
116
|
+
ee.on('test', vi.fn());
|
|
117
|
+
expect(() => ee.off('test', vi.fn())).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('subscribe', () => {
|
|
122
|
+
it('should catch any emitted event', () => {
|
|
123
|
+
const listener = vi.fn();
|
|
124
|
+
ee.subscribe(listener);
|
|
125
|
+
ee.emit('test', 1, true);
|
|
126
|
+
ee.emit('hey');
|
|
127
|
+
expect(listener).toBeCalledTimes(2);
|
|
128
|
+
expect(listener).toHaveBeenNthCalledWith(1, 'test', 1, true);
|
|
129
|
+
expect(listener).toHaveBeenNthCalledWith(2, 'hey');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('unsubscribe', () => {
|
|
134
|
+
it('should not emit event if it was unbound', () => {
|
|
135
|
+
const listener = vi.fn();
|
|
136
|
+
ee.subscribe(listener);
|
|
137
|
+
ee.unsubscribe(listener);
|
|
138
|
+
ee.emit('test', 1, true);
|
|
139
|
+
expect(listener).not.toBeCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should not do anything if received not bound listener', () => {
|
|
143
|
+
expect(() => ee.unsubscribe(vi.fn())).not.toThrow();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { HapticFeedback } from '../HapticFeedback';
|
|
4
|
+
|
|
5
|
+
describe('impactOccurred', () => {
|
|
6
|
+
it('should call "web_app_trigger_haptic_feedback" method with { type: "impact", style: {{style}} }', () => {
|
|
7
|
+
const postEvent = vi.fn();
|
|
8
|
+
const haptic = new HapticFeedback('', postEvent);
|
|
9
|
+
|
|
10
|
+
expect(postEvent).toHaveBeenCalledTimes(0);
|
|
11
|
+
haptic.impactOccurred('heavy');
|
|
12
|
+
expect(postEvent).toHaveBeenCalledTimes(1);
|
|
13
|
+
expect(postEvent).toHaveBeenCalledWith('web_app_trigger_haptic_feedback', {
|
|
14
|
+
type: 'impact',
|
|
15
|
+
impact_style: 'heavy',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('notificationOccurred', () => {
|
|
21
|
+
it('should call "web_app_trigger_haptic_feedback" method with { type: "notification", notification_type: {{type}} }', () => {
|
|
22
|
+
const postEvent = vi.fn();
|
|
23
|
+
const haptic = new HapticFeedback('', postEvent);
|
|
24
|
+
|
|
25
|
+
expect(postEvent).toHaveBeenCalledTimes(0);
|
|
26
|
+
haptic.notificationOccurred('success');
|
|
27
|
+
expect(postEvent).toHaveBeenCalledTimes(1);
|
|
28
|
+
expect(postEvent).toHaveBeenCalledWith('web_app_trigger_haptic_feedback', {
|
|
29
|
+
type: 'notification',
|
|
30
|
+
notification_type: 'success',
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('selectionChanged', () => {
|
|
36
|
+
it('should call "web_app_trigger_haptic_feedback" method with { type: "selection_change" }', () => {
|
|
37
|
+
const postEvent = vi.fn();
|
|
38
|
+
const haptic = new HapticFeedback('', postEvent);
|
|
39
|
+
|
|
40
|
+
expect(postEvent).toHaveBeenCalledTimes(0);
|
|
41
|
+
haptic.selectionChanged();
|
|
42
|
+
expect(postEvent).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(postEvent).toHaveBeenCalledWith('web_app_trigger_haptic_feedback', {
|
|
44
|
+
type: 'selection_change',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('supports', () => {
|
|
50
|
+
describe('impactOccurred / notificationOccurred / selectionChanged', () => {
|
|
51
|
+
it('should return true in case, HapticFeedback version is 6.1 or higher. False, otherwise', () => {
|
|
52
|
+
const haptic1 = new HapticFeedback('6.0');
|
|
53
|
+
expect(haptic1.supports('impactOccurred')).toBe(false);
|
|
54
|
+
expect(haptic1.supports('notificationOccurred')).toBe(false);
|
|
55
|
+
expect(haptic1.supports('selectionChanged')).toBe(false);
|
|
56
|
+
|
|
57
|
+
const haptic2 = new HapticFeedback('6.1');
|
|
58
|
+
expect(haptic2.supports('impactOccurred')).toBe(true);
|
|
59
|
+
expect(haptic2.supports('notificationOccurred')).toBe(true);
|
|
60
|
+
expect(haptic2.supports('selectionChanged')).toBe(true);
|
|
61
|
+
|
|
62
|
+
const haptic3 = new HapticFeedback('6.2');
|
|
63
|
+
expect(haptic3.supports('impactOccurred')).toBe(true);
|
|
64
|
+
expect(haptic3.supports('notificationOccurred')).toBe(true);
|
|
65
|
+
expect(haptic3.supports('selectionChanged')).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -154,6 +154,7 @@ export type { RequestId, CreateRequestIdFunc } from './types/index.js';
|
|
|
154
154
|
export { Utils } from './utils/index.js';
|
|
155
155
|
export { compareVersions, type Version } from './version/index.js';
|
|
156
156
|
export {
|
|
157
|
+
isStableViewportPlatform,
|
|
157
158
|
requestViewport,
|
|
158
159
|
Viewport,
|
|
159
160
|
type RequestViewportResult,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { mockSessionStorageGetItem } from 'test-utils';
|
|
2
|
+
import type { SpyInstance } from 'vitest';
|
|
3
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createWindow } from '../../../../test-utils/createWindow';
|
|
6
|
+
import { requestViewport } from '../../../viewport/requestViewport';
|
|
7
|
+
import { createViewport } from '../createViewport';
|
|
8
|
+
|
|
9
|
+
vi.mock('../../../viewport/requestViewport', () => {
|
|
10
|
+
return {
|
|
11
|
+
requestViewport: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
type RequestViewport = typeof requestViewport;
|
|
16
|
+
|
|
17
|
+
const requestViewportMock = requestViewport as unknown as SpyInstance<Parameters<RequestViewport>, ReturnType<RequestViewport>>;
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
requestViewportMock.mockReset();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should create Viewport from the data located in the storage if page was reloaded and storage has viewport data', () => {
|
|
25
|
+
mockSessionStorageGetItem('{"height":992,"isExpanded":false,"stableHeight":992,"width":320}');
|
|
26
|
+
expect(createViewport(true, 'macos', vi.fn(), false)).toMatchObject({
|
|
27
|
+
height: 992,
|
|
28
|
+
isExpanded: false,
|
|
29
|
+
stableHeight: 992,
|
|
30
|
+
width: 320,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return viewport with window data if page was not reloaded or storage is missing required data, and platform has a stable viewport', () => {
|
|
35
|
+
createWindow({ innerHeight: 1000, innerWidth: 2000 });
|
|
36
|
+
expect(createViewport(false, 'macos', vi.fn(), false)).toMatchObject({
|
|
37
|
+
height: 1000,
|
|
38
|
+
isExpanded: true,
|
|
39
|
+
stableHeight: 1000,
|
|
40
|
+
width: 2000,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(createViewport(true, 'macos', vi.fn(), false)).toMatchObject({
|
|
44
|
+
height: 1000,
|
|
45
|
+
isExpanded: true,
|
|
46
|
+
stableHeight: 1000,
|
|
47
|
+
width: 2000,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should create Viewport instance from the result of calling requestViewport function if page was not reloaded, platform has no stable viewport and initialization is complete', () => {
|
|
52
|
+
requestViewportMock.mockImplementation(async () => ({
|
|
53
|
+
height: 922,
|
|
54
|
+
isStateStable: false,
|
|
55
|
+
width: 800,
|
|
56
|
+
isExpanded: false,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
expect(createViewport(false, 'android', vi.fn(), true)).resolves.toMatchObject({
|
|
60
|
+
height: 922,
|
|
61
|
+
isExpanded: false,
|
|
62
|
+
stableHeight: 0,
|
|
63
|
+
width: 800,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
requestViewportMock.mockImplementation(async () => ({
|
|
67
|
+
height: 111,
|
|
68
|
+
isStateStable: true,
|
|
69
|
+
width: 222,
|
|
70
|
+
isExpanded: true,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
expect(createViewport(false, 'android', vi.fn(), true)).resolves.toMatchObject({
|
|
74
|
+
height: 111,
|
|
75
|
+
isExpanded: true,
|
|
76
|
+
stableHeight: 111,
|
|
77
|
+
width: 222,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return viewport with zeroes if page was reloaded, storage doesnt contain data, platform has no stable viewport and initialization is not complete', () => {
|
|
82
|
+
requestViewportMock.mockImplementation(async () => ({
|
|
83
|
+
height: 922,
|
|
84
|
+
isStateStable: false,
|
|
85
|
+
width: 800,
|
|
86
|
+
isExpanded: false,
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
mockSessionStorageGetItem(null);
|
|
90
|
+
expect(createViewport(true, 'android', vi.fn(), false)).toMatchObject({
|
|
91
|
+
height: 0,
|
|
92
|
+
isExpanded: false,
|
|
93
|
+
stableHeight: 0,
|
|
94
|
+
width: 0,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,53 +1,19 @@
|
|
|
1
1
|
import { getStorageValue, saveStorageValue } from '~/storage.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isStableViewportPlatform,
|
|
4
|
+
requestViewport,
|
|
5
|
+
Viewport,
|
|
6
|
+
type ViewportProps,
|
|
7
|
+
} from '~/viewport/index.js';
|
|
3
8
|
import type { PostEvent } from '~/bridge/index.js';
|
|
4
9
|
import type { Platform } from '~/types/index.js';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @param platform - platform identifier.
|
|
10
|
-
*/
|
|
11
|
-
function isRequestSupportedPlatform(platform: Platform): boolean {
|
|
12
|
-
return !['macos', 'web', 'weba'].includes(platform);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Attempts to create Viewport instance using known parameters and local storage.
|
|
17
|
-
* @param isPageReload - was page reloaded.
|
|
18
|
-
* @param platform - platform identifier.
|
|
19
|
-
* @param postEvent - Bridge postEvent function.
|
|
20
|
-
*/
|
|
21
|
-
function tryCreate(
|
|
22
|
-
isPageReload: boolean,
|
|
23
|
-
platform: Platform,
|
|
24
|
-
postEvent: PostEvent,
|
|
25
|
-
): Viewport | null {
|
|
26
|
-
if (isPageReload || !isRequestSupportedPlatform(platform)) {
|
|
27
|
-
return new Viewport({
|
|
28
|
-
height: window.innerHeight,
|
|
29
|
-
isExpanded: true,
|
|
30
|
-
postEvent,
|
|
31
|
-
stableHeight: window.innerHeight,
|
|
32
|
-
width: window.innerWidth,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const state = getStorageValue('viewport');
|
|
37
|
-
if (state) {
|
|
38
|
-
return new Viewport({ ...state, postEvent });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Synchronizes specified Viewport instance with Telegram application and always saves its state
|
|
46
|
-
* in local storage.
|
|
47
|
-
* @param viewport - Viewport instance.
|
|
12
|
+
* Creates new bound instance of the Viewport component.
|
|
13
|
+
* @param props - properties to create new instance.
|
|
48
14
|
*/
|
|
49
|
-
function
|
|
50
|
-
viewport
|
|
15
|
+
function instantiate(props: ViewportProps): Viewport {
|
|
16
|
+
const viewport = new Viewport(props);
|
|
51
17
|
|
|
52
18
|
// TODO: Should probably use throttle for height.
|
|
53
19
|
viewport.on('change', () => saveStorageValue('viewport', {
|
|
@@ -57,59 +23,72 @@ function bind(viewport: Viewport): Viewport {
|
|
|
57
23
|
width: viewport.width,
|
|
58
24
|
}));
|
|
59
25
|
|
|
26
|
+
viewport.listen();
|
|
27
|
+
|
|
60
28
|
return viewport;
|
|
61
29
|
}
|
|
62
30
|
|
|
63
31
|
/**
|
|
64
|
-
* Creates Viewport instance using its actual state from the
|
|
65
|
-
* with default parameters.
|
|
32
|
+
* Creates Viewport instance using its actual state from the Telegram application.
|
|
66
33
|
* @param isPageReload - was page reloaded.
|
|
67
34
|
* @param platform - platform identifier.
|
|
68
35
|
* @param postEvent - Bridge postEvent function.
|
|
36
|
+
* @param complete - is initialization complete.
|
|
69
37
|
*/
|
|
70
|
-
export function
|
|
38
|
+
export function createViewport(
|
|
71
39
|
isPageReload: boolean,
|
|
72
40
|
platform: Platform,
|
|
73
41
|
postEvent: PostEvent,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
stableHeight: 0,
|
|
82
|
-
}),
|
|
83
|
-
);
|
|
42
|
+
complete: boolean,
|
|
43
|
+
): Viewport | Promise<Viewport> {
|
|
44
|
+
// If page was reloaded, we expect viewport to be restored from the storage.
|
|
45
|
+
const state = isPageReload ? getStorageValue('viewport') : null;
|
|
46
|
+
if (state) {
|
|
47
|
+
return instantiate({ ...state, postEvent });
|
|
48
|
+
}
|
|
84
49
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
50
|
+
// If platform has a stable viewport, it means we could instantiate Viewport using
|
|
51
|
+
// the window global object properties.
|
|
52
|
+
if (isStableViewportPlatform(platform)) {
|
|
53
|
+
return instantiate({
|
|
54
|
+
height: window.innerHeight,
|
|
55
|
+
isExpanded: true,
|
|
56
|
+
postEvent,
|
|
57
|
+
stableHeight: window.innerHeight,
|
|
58
|
+
width: window.innerWidth,
|
|
89
59
|
});
|
|
90
60
|
}
|
|
91
61
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*/
|
|
101
|
-
export async function createViewportAsync(
|
|
102
|
-
isPageReload: boolean,
|
|
103
|
-
platform: Platform,
|
|
104
|
-
postEvent: PostEvent,
|
|
105
|
-
): Promise<Viewport> {
|
|
106
|
-
return bind(
|
|
107
|
-
tryCreate(isPageReload, platform, postEvent)
|
|
108
|
-
|| await requestViewport({ postEvent, timeout: 100 })
|
|
109
|
-
.then(({ height, isStateStable, ...rest }) => new Viewport({
|
|
62
|
+
// If initialization is complete, we have to create Viewport instance using its actual
|
|
63
|
+
// state from the Telegram application.
|
|
64
|
+
if (complete) {
|
|
65
|
+
return requestViewport({
|
|
66
|
+
postEvent,
|
|
67
|
+
timeout: 5000,
|
|
68
|
+
})
|
|
69
|
+
.then(({ height, isStateStable, ...rest }) => instantiate({
|
|
110
70
|
...rest,
|
|
111
71
|
height,
|
|
112
72
|
stableHeight: isStateStable ? height : 0,
|
|
113
|
-
}))
|
|
114
|
-
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Otherwise we have no sources to get viewport properties from. In this case we just
|
|
77
|
+
// return some "empty" Viewport instance and synchronize it in the background.
|
|
78
|
+
const viewport = instantiate({
|
|
79
|
+
width: 0,
|
|
80
|
+
height: 0,
|
|
81
|
+
isExpanded: false,
|
|
82
|
+
postEvent,
|
|
83
|
+
stableHeight: 0,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/* c8 ignore start */
|
|
87
|
+
viewport.sync({ postEvent, timeout: 5000 }).catch((e) => {
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.error('Unable to actualize viewport state', e);
|
|
90
|
+
});
|
|
91
|
+
/* c8 ignore stop */
|
|
92
|
+
|
|
93
|
+
return viewport;
|
|
115
94
|
}
|