@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.
Files changed (97) hide show
  1. package/dist/dts/index.d.ts +1 -1
  2. package/dist/dts/init/creators/createViewport.d.ts +2 -9
  3. package/dist/dts/init/init.d.ts +2 -0
  4. package/dist/dts/init/types.d.ts +7 -4
  5. package/dist/dts/launch-params/index.d.ts +1 -0
  6. package/dist/dts/launch-params/retrieveFromUrl.d.ts +6 -0
  7. package/dist/dts/launch-params/types.d.ts +12 -8
  8. package/dist/dts/types/platform.d.ts +1 -1
  9. package/dist/dts/viewport/index.d.ts +1 -0
  10. package/dist/dts/viewport/isStableViewportPlatform.d.ts +7 -0
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.iife.js +1 -1
  14. package/dist/index.iife.js.map +1 -1
  15. package/dist/index.mjs +469 -467
  16. package/dist/index.mjs.map +1 -1
  17. package/package.json +2 -2
  18. package/src/__tests__/globals.ts +39 -0
  19. package/src/back-button/__tests__/BackButton.ts +129 -0
  20. package/src/bridge/__tests__/request.ts +236 -0
  21. package/src/bridge/env/__tests__/hasExternalNotify.ts +15 -0
  22. package/src/bridge/env/__tests__/hasWebviewProxy.ts +15 -0
  23. package/src/bridge/env/__tests__/isIframe.ts +30 -0
  24. package/src/bridge/events/__tests__/createEmitter.ts +143 -0
  25. package/src/bridge/events/__tests__/off.ts +34 -0
  26. package/src/bridge/events/__tests__/on.ts +49 -0
  27. package/src/bridge/events/__tests__/onTelegramEvent.ts +51 -0
  28. package/src/bridge/events/__tests__/once.ts +64 -0
  29. package/src/bridge/events/__tests__/singletonEmitter.ts +22 -0
  30. package/src/bridge/events/__tests__/subscribe.ts +49 -0
  31. package/src/bridge/events/__tests__/unsubscribe.ts +34 -0
  32. package/src/bridge/events/parsers/__tests__/clipboardTextReceived.ts +21 -0
  33. package/src/bridge/events/parsers/__tests__/invoiceClosed.ts +12 -0
  34. package/src/bridge/events/parsers/__tests__/popupClosed.ts +10 -0
  35. package/src/bridge/events/parsers/__tests__/qrTextReceived.ts +9 -0
  36. package/src/bridge/events/parsers/__tests__/theme-changed.ts +42 -0
  37. package/src/bridge/events/parsers/__tests__/viewportChanged.ts +49 -0
  38. package/src/bridge/methods/__tests__/createPostEvent.ts +37 -0
  39. package/src/bridge/methods/__tests__/postEvent.ts +137 -0
  40. package/src/classnames/__tests__/classNames.ts +20 -0
  41. package/src/classnames/__tests__/mergeClassNames.ts +21 -0
  42. package/src/closing-behavior/__tests__/ClosingBehavior.ts +86 -0
  43. package/src/colors/__tests__/isColorDark.ts +12 -0
  44. package/src/colors/__tests__/isRGB.ts +13 -0
  45. package/src/colors/__tests__/isRGBShort.ts +13 -0
  46. package/src/colors/__tests__/toRGB.ts +23 -0
  47. package/src/event-emitter/__tests__/EventEmitter.ts +145 -0
  48. package/src/haptic-feedback/__tests__/HapticFeedback.ts +68 -0
  49. package/src/index.ts +1 -0
  50. package/src/init/creators/__tests__/createViewport.ts +96 -0
  51. package/src/init/creators/createViewport.ts +60 -81
  52. package/src/init/init.ts +13 -15
  53. package/src/init/types.ts +8 -4
  54. package/src/init-data/__tests__/InitData.ts +98 -0
  55. package/src/init-data/__tests__/chatParser.ts +102 -0
  56. package/src/init-data/__tests__/initDataParser.ts +136 -0
  57. package/src/init-data/__tests__/parseInitData.ts +136 -0
  58. package/src/init-data/__tests__/userParser.ts +96 -0
  59. package/src/launch-params/__tests__/retrieveFromUrl.ts +19 -0
  60. package/src/launch-params/index.ts +1 -0
  61. package/src/launch-params/launchParamsParser.ts +4 -0
  62. package/src/launch-params/retrieveFromLocation.ts +2 -2
  63. package/src/launch-params/retrieveFromPerformance.ts +2 -7
  64. package/src/launch-params/retrieveFromUrl.ts +19 -0
  65. package/src/launch-params/types.ts +13 -8
  66. package/src/logger/__tests__/Logger.ts +107 -0
  67. package/src/main-button/__tests__/MainButton.ts +346 -0
  68. package/src/mini-app/__tests__/MiniApp.ts +140 -0
  69. package/src/misc/__tests__/isRecord.ts +21 -0
  70. package/src/navigation/HashNavigator/__tests__/HashNavigator.ts +144 -0
  71. package/src/navigation/HashNavigator/__tests__/drop.ts +42 -0
  72. package/src/navigation/HashNavigator/__tests__/go.ts +9 -0
  73. package/src/parsing/__tests__/ArrayValueParser.ts +18 -0
  74. package/src/parsing/__tests__/toRecord.ts +10 -0
  75. package/src/parsing/parsers/__tests__/array.ts +39 -0
  76. package/src/parsing/parsers/__tests__/boolean.ts +31 -0
  77. package/src/parsing/parsers/__tests__/date.ts +25 -0
  78. package/src/parsing/parsers/__tests__/json.ts +80 -0
  79. package/src/parsing/parsers/__tests__/number.ts +23 -0
  80. package/src/parsing/parsers/__tests__/rgb.ts +22 -0
  81. package/src/parsing/parsers/__tests__/searchParams.ts +105 -0
  82. package/src/parsing/parsers/__tests__/string.ts +25 -0
  83. package/src/popup/__tests__/Popup.ts +130 -0
  84. package/src/popup/__tests__/preparePopupParams.ts +85 -0
  85. package/src/supports/__tests__/supports.ts +123 -0
  86. package/src/theme-params/__tests__/keys.ts +19 -0
  87. package/src/theme-params/__tests__/parseThemeParams.ts +29 -0
  88. package/src/theme-params/__tests__/serializeThemeParams.ts +29 -0
  89. package/src/theme-params/__tests__/themeParamsParser.ts +29 -0
  90. package/src/timeout/__tests__/isTimeoutError.ts +9 -0
  91. package/src/timeout/__tests__/withTimeout.ts +28 -0
  92. package/src/types/platform.ts +2 -2
  93. package/src/version/__tests__/compareVersions.ts +19 -0
  94. package/src/viewport/__tests__/isStableViewportPlatform.ts +15 -0
  95. package/src/viewport/__tests__/utils.ts +12 -0
  96. package/src/viewport/index.ts +1 -0
  97. package/src/viewport/isStableViewportPlatform.ts +10 -0
@@ -0,0 +1,21 @@
1
+ import { expect, it } from 'vitest';
2
+
3
+ import { isRecord } from '../isRecord';
4
+
5
+ it('should return false for non-object value', () => {
6
+ [true, 123, 'abc'].forEach((v) => {
7
+ expect(isRecord(v)).toBe(false);
8
+ });
9
+ });
10
+
11
+ it('should return false for null value', () => {
12
+ expect(isRecord(null)).toBe(false);
13
+ });
14
+
15
+ it('should return false for array', () => {
16
+ expect(isRecord([])).toBe(false);
17
+ });
18
+
19
+ it('should return true for object', () => {
20
+ expect(isRecord({ a: true })).toBe(true);
21
+ });
@@ -0,0 +1,144 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { HashNavigator } from '../HashNavigator';
4
+
5
+ // TODO: Add more tests.
6
+
7
+ describe('constructor', () => {
8
+ it('should throw an error if entries list is empty', () => {
9
+ expect(() => new HashNavigator([], 0)).toThrow('Entries list should not be empty.');
10
+ });
11
+
12
+ it('should throw an error if cursor equals to higher than entries count', () => {
13
+ expect(() => new HashNavigator([{ pathname: '/' }], 1))
14
+ .toThrow('Cursor should be less than entries count.');
15
+ expect(() => new HashNavigator([{ pathname: '/' }], 2))
16
+ .toThrow('Cursor should be less than entries count.');
17
+ });
18
+ });
19
+
20
+ describe('methods', () => {
21
+ // describe('go', () => {
22
+ // });
23
+ //
24
+ // describe('forward', () => {
25
+ // });
26
+ //
27
+ // describe('back', () => {
28
+ // });
29
+
30
+ describe('getEntries', () => {
31
+ it('should return deep clone of navigator entries', () => {
32
+ const initialEntries = [{
33
+ search: '?b',
34
+ hash: '#c',
35
+ pathname: '/a',
36
+ }];
37
+ const navigator = new HashNavigator(initialEntries, 0);
38
+ const entries = navigator.getEntries();
39
+
40
+ expect(entries).toStrictEqual(initialEntries);
41
+ expect(entries).not.toBe(initialEntries);
42
+
43
+ expect(entries[0]).toStrictEqual(initialEntries[0]);
44
+ expect(entries[0]).not.toBe(initialEntries[0]);
45
+ });
46
+ });
47
+
48
+ // describe('push', () => {
49
+ // });
50
+ //
51
+ describe('replace', () => {
52
+ it('should replace current entry', () => {
53
+ const navigator = new HashNavigator([{
54
+ search: '?b',
55
+ hash: '#c',
56
+ pathname: '/a',
57
+ }], 0);
58
+ const [prevEntry] = navigator.getEntries();
59
+
60
+ expect(navigator.cursor).toBe(0);
61
+ navigator.replace('/b');
62
+ const [entry] = navigator.getEntries();
63
+
64
+ expect(prevEntry).not.toStrictEqual(entry);
65
+ expect(navigator.cursor).toBe(0);
66
+ });
67
+ });
68
+ });
69
+
70
+ describe('getters', () => {
71
+ describe('pathname', () => {
72
+ it('should return current entry pathname', () => {
73
+ expect(new HashNavigator([{ pathname: '/abc' }], 0).pathname).toBe('/abc');
74
+ });
75
+ });
76
+
77
+ describe('search', () => {
78
+ it('should return current entry search', () => {
79
+ expect(
80
+ new HashNavigator([{
81
+ search: '?a=1',
82
+ pathname: '/',
83
+ }], 0).search,
84
+ ).toBe('?a=1');
85
+ });
86
+ });
87
+
88
+ describe('hash', () => {
89
+ it('should return current entry hash', () => {
90
+ expect(
91
+ new HashNavigator([{
92
+ hash: '#abc',
93
+ pathname: '/',
94
+ }], 0).hash,
95
+ ).toBe('#abc');
96
+ });
97
+ });
98
+
99
+ describe('path', () => {
100
+ it('should combine current entry pathname, search and hash', () => {
101
+ expect(
102
+ new HashNavigator([{
103
+ search: '?b',
104
+ hash: '#c',
105
+ pathname: '/a',
106
+ }], 0).path,
107
+ ).toBe('/a?b#c');
108
+ });
109
+ });
110
+
111
+ describe('canGoBack', () => {
112
+ it('should return false if cursor === 0', () => {
113
+ expect(
114
+ new HashNavigator([{ pathname: '/' }], 0).canGoBack,
115
+ ).toBe(false);
116
+ });
117
+
118
+ it('should return true if cursor > 0', () => {
119
+ expect(
120
+ new HashNavigator([
121
+ { pathname: '/a' },
122
+ { pathname: '/b' },
123
+ ], 1).canGoBack,
124
+ ).toBe(true);
125
+ });
126
+ });
127
+
128
+ describe('canGoForward', () => {
129
+ it('should return false if cursor === entries.length - 1', () => {
130
+ expect(
131
+ new HashNavigator([{ pathname: '/' }], 0).canGoForward,
132
+ ).toBe(false);
133
+ });
134
+
135
+ it('should return true if cursor < entries.length - 1', () => {
136
+ expect(
137
+ new HashNavigator([
138
+ { pathname: '/a' },
139
+ { pathname: '/b' },
140
+ ], 0).canGoForward,
141
+ ).toBe(true);
142
+ });
143
+ });
144
+ });
@@ -0,0 +1,42 @@
1
+ import { afterEach, expect, it, vi } from 'vitest';
2
+
3
+ import { drop } from '../drop';
4
+
5
+ // TODO: Add more tests.
6
+
7
+ function mockHistoryLength(length: number) {
8
+ vi
9
+ .spyOn(window.history, 'length', 'get')
10
+ .mockImplementation(() => length);
11
+ }
12
+
13
+ function mockPushState(impl?: History['pushState']) {
14
+ const spy = vi.spyOn(window.history, 'pushState');
15
+
16
+ if (impl) {
17
+ spy.mockImplementation(impl);
18
+ }
19
+ }
20
+
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ it('should do nothing in case, history contains only 1 element', () => {
26
+ const pushStateSpy = vi.fn();
27
+ mockHistoryLength(1);
28
+ mockPushState(pushStateSpy);
29
+
30
+ expect(pushStateSpy).not.toHaveBeenCalled();
31
+ });
32
+
33
+ it('should push empty state', () => {
34
+ const pushStateSpy = vi.fn();
35
+ mockHistoryLength(2);
36
+ mockPushState(pushStateSpy);
37
+
38
+ drop();
39
+
40
+ expect(pushStateSpy).toHaveBeenCalledOnce();
41
+ expect(pushStateSpy).toHaveBeenCalledWith(null, '');
42
+ });
@@ -0,0 +1,9 @@
1
+ import { expect, it } from 'vitest';
2
+
3
+ import { go } from '../go';
4
+
5
+ // TODO: Add more tests.
6
+
7
+ it('should return true if delta is 0', () => {
8
+ expect(go(0)).resolves.toBe(true);
9
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { ArrayValueParser } from '../ArrayValueParser';
4
+ import { string } from '../parsers';
5
+
6
+ describe('constructor', () => {
7
+ it('should apply parser value directly if it is function', () => {
8
+ const parser = new ArrayValueParser(() => 'Hello!', false);
9
+
10
+ expect(parser.parse(['abc'])).toStrictEqual(['Hello!']);
11
+ });
12
+
13
+ it('should apply parser "parse" method directly if it is ValueParser', () => {
14
+ const parser = new ArrayValueParser(string(), false);
15
+
16
+ expect(parser.parse(['abc'])).toStrictEqual(['abc']);
17
+ });
18
+ });
@@ -0,0 +1,10 @@
1
+ import { expect, it } from 'vitest';
2
+
3
+ import { toRecord } from '../index';
4
+
5
+ it('should throw an error in case, passed value is not JSON object or not JSON object converted to string', () => {
6
+ expect(() => toRecord('')).toThrow();
7
+ expect(() => toRecord(true)).toThrow();
8
+ expect(() => toRecord('{}')).not.toThrow();
9
+ expect(() => toRecord({})).not.toThrow();
10
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { array } from '../array';
4
+ import { string } from '../string';
5
+
6
+ it('should return value in case, it is array', () => {
7
+ expect(array().parse(['abc'])).toStrictEqual(['abc']);
8
+ });
9
+
10
+ it('should correctly parse JSON Array presented as string', () => {
11
+ expect(array().parse('["abc"]')).toStrictEqual(['abc']);
12
+ });
13
+
14
+ it('should throw an error in case, passed value is not array', () => {
15
+ expect(() => array().parse(true)).toThrow();
16
+ expect(() => array().parse(1)).toThrow();
17
+ expect(() => array().parse('okay')).toThrow();
18
+ expect(() => array().parse({})).toThrow();
19
+ });
20
+
21
+ describe('of', () => {
22
+ it('should correctly apply item parser to each array item', () => {
23
+ expect(array().of(string()).parse(['abc'])).toStrictEqual(['abc']);
24
+ });
25
+
26
+ it('should throw an error in case, item parser was unable to parse value', () => {
27
+ expect(() => array().of(string()).parse(['abc', {}])).toThrow();
28
+ });
29
+
30
+ it('should use parsing function directly if it is not ValueParser', () => {
31
+ expect(array().of(() => 'Hello!').parse(['abc'])).toStrictEqual(['Hello!']);
32
+ });
33
+ });
34
+
35
+ describe('optional', () => {
36
+ it('should return undefined if value is undefined', () => {
37
+ expect(array().optional().parse(undefined)).toBe(undefined);
38
+ });
39
+ });
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { boolean } from '../boolean';
4
+
5
+ it('should return value in case, it has type boolean', () => {
6
+ expect(boolean().parse(true)).toBe(true);
7
+ expect(boolean().parse(false)).toBe(false);
8
+ });
9
+
10
+ it('should return true in case, value string representation equals "1" or "true"', () => {
11
+ expect(boolean().parse(1)).toBe(true);
12
+ expect(boolean().parse('1')).toBe(true);
13
+ expect(boolean().parse('true')).toBe(true);
14
+ });
15
+
16
+ it('should return false in case, value string representation equals "0" or "false"', () => {
17
+ expect(boolean().parse(0)).toBe(false);
18
+ expect(boolean().parse('0')).toBe(false);
19
+ expect(boolean().parse('false')).toBe(false);
20
+ });
21
+
22
+ it('should throw an error in case, passed value is not of type boolean and its string representation is not "0", "1", "true" or "false"', () => {
23
+ expect(() => boolean().parse('true!')).toThrow();
24
+ expect(() => boolean().parse({})).toThrow();
25
+ });
26
+
27
+ describe('optional', () => {
28
+ it('should return undefined if value is undefined', () => {
29
+ expect(boolean().optional().parse(undefined)).toBe(undefined);
30
+ });
31
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { date } from '../date';
4
+
5
+ it('should return value in case, it is instance of Date', () => {
6
+ const d = new Date();
7
+ expect(date().parse(d)).toBe(d);
8
+ });
9
+
10
+ it('should throw an error in case, passed value cannot be converted to number', () => {
11
+ expect(() => date().parse('true')).toThrow();
12
+ expect(() => date().parse({})).toThrow();
13
+ });
14
+
15
+ it('should create date multiplying value by 1000 in case this value can be converted to number', () => {
16
+ const a = new Date(1000);
17
+ expect(date().parse('1')).toStrictEqual(a);
18
+ expect(date().parse(1)).toStrictEqual(a);
19
+ });
20
+
21
+ describe('optional', () => {
22
+ it('should return undefined if value is undefined', () => {
23
+ expect(date().optional().parse(undefined)).toBe(undefined);
24
+ });
25
+ });
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { boolean } from '../boolean';
4
+ import { json } from '../json';
5
+ import { number } from '../number';
6
+ import { string } from '../string';
7
+
8
+ it('should throw an error in case, passed value is not JSON object or not JSON object converted to string', () => {
9
+ const parser = json({});
10
+ expect(() => parser.parse('')).toThrow();
11
+ expect(() => parser.parse(true)).toThrow();
12
+ expect(() => parser.parse('{}')).not.toThrow();
13
+ expect(() => parser.parse({})).not.toThrow();
14
+ });
15
+
16
+ it('should throw an error in case, passed value does not contain required field presented in schema', () => {
17
+ const parser = json({ prop: string() });
18
+ expect(() => parser.parse({})).toThrow();
19
+ });
20
+
21
+ it('should ignore field in case its value is undefined', () => {
22
+ const parser = json<{ prop?: string }>({ prop: undefined });
23
+ expect(parser.parse({})).toStrictEqual({});
24
+ });
25
+
26
+ describe('field definition as object', () => {
27
+ it('should extract field value from the specified property "from"', () => {
28
+ const parser = json({
29
+ a: {
30
+ type: (value) => value,
31
+ from: 'b',
32
+ },
33
+ });
34
+ expect(parser.parse({ b: 'Hello there!' })).toStrictEqual({ a: 'Hello there!' });
35
+ });
36
+
37
+ it('should extract field value from schema field name, if "from" was not specified', () => {
38
+ const parser = json({
39
+ a: {
40
+ type: (value) => value,
41
+ },
42
+ });
43
+ expect(parser.parse({ a: 'Hello there!' })).toStrictEqual({ a: 'Hello there!' });
44
+ });
45
+
46
+ it('should apply parser passed as ValueParser', () => {
47
+ const parser = json({
48
+ a: {
49
+ type: string(),
50
+ },
51
+ });
52
+ expect(parser.parse({ a: 'Hello there!' })).toStrictEqual({ a: 'Hello there!' });
53
+ });
54
+ });
55
+
56
+ it('should also apply parser presented as function', () => {
57
+ const parser = json({
58
+ prop: () => 'static value',
59
+ });
60
+ expect(parser.parse({ prop: 999 })).toStrictEqual({ prop: 'static value' });
61
+ });
62
+
63
+ it('should throw an error in case, passed value contains field of different type presented in schema', () => {
64
+ const parser = json({ prop: string() });
65
+ expect(() => parser.parse({ prop: {} })).toThrow();
66
+ });
67
+
68
+ it('should correctly parse built-in types', () => {
69
+ const parser = json({
70
+ bool: boolean(),
71
+ string: string(),
72
+ number: number(),
73
+ });
74
+ const obj = {
75
+ bool: true,
76
+ string: '123',
77
+ number: 999,
78
+ };
79
+ expect(parser.parse(obj)).toStrictEqual(obj);
80
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { number } from '../number';
4
+
5
+ it('should return value in case, it has type number', () => {
6
+ expect(number().parse(9992)).toBe(9992);
7
+ });
8
+
9
+ it('should return value converted to number in case, it is number converted to string', () => {
10
+ expect(number().parse('9992')).toBe(9992);
11
+ });
12
+
13
+ it('should throw an error in case, passed value is not of type number or does not represent number converted to string', () => {
14
+ expect(() => number().parse(true)).toThrow();
15
+ expect(() => number().parse('vvv')).toThrow();
16
+ expect(() => number().parse({})).toThrow();
17
+ });
18
+
19
+ describe('optional', () => {
20
+ it('should return undefined if value is undefined', () => {
21
+ expect(number().optional().parse(undefined)).toBe(undefined);
22
+ });
23
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { rgb } from '../rgb';
4
+
5
+ it('should return value in case, it represents RGB color', () => {
6
+ expect(rgb().parse('#fff')).toBe('#ffffff');
7
+ });
8
+
9
+ it('should throw an error in case, passed value is not of type string', () => {
10
+ expect(() => rgb().parse(true)).toThrow();
11
+ expect(() => rgb().parse({})).toThrow();
12
+ });
13
+
14
+ it('should throw an error in case, passed value does not represent RGB string', () => {
15
+ expect(() => rgb().parse('my custom string')).toThrow();
16
+ });
17
+
18
+ describe('optional', () => {
19
+ it('should return undefined if value is undefined', () => {
20
+ expect(rgb().optional().parse(undefined)).toBe(undefined);
21
+ });
22
+ });
@@ -0,0 +1,105 @@
1
+ import { expect, it } from 'vitest';
2
+
3
+ import { date } from '../date';
4
+ import { searchParams } from '../searchParams';
5
+ import { string } from '../string';
6
+
7
+ it('should throw an error in case, passed value is not of type string or URLSearchParams', () => {
8
+ const parser = searchParams({});
9
+ expect(() => parser.parse(true)).toThrow();
10
+ expect(() => parser.parse({})).toThrow();
11
+ expect(() => parser.parse('true')).not.toThrow();
12
+ expect(() => parser.parse(new URLSearchParams())).not.toThrow();
13
+ });
14
+
15
+ it('should throw an error in case, passed value does not contain required field presented in schema', () => {
16
+ const parser = searchParams({ prop: string() });
17
+
18
+ try {
19
+ parser.parse('abc=123');
20
+ } catch (e) {
21
+ expect(e).toMatchObject({
22
+ message: 'Unable to parse value',
23
+ cause: {
24
+ message: 'Unable to parse field "prop" as string',
25
+ cause: {
26
+ message: 'Unable to parse value as string',
27
+ cause: {
28
+ message: 'Value has unexpected type',
29
+ },
30
+ },
31
+ },
32
+ });
33
+ }
34
+ expect.assertions(1);
35
+ });
36
+
37
+ it('should not throw an error in case, passed value does not contain optional field presented in schema', () => {
38
+ const parser = searchParams<{ prop?: string }>({
39
+ prop: string().optional(),
40
+ });
41
+ expect(parser.parse('')).toEqual({});
42
+ expect(parser.parse('prop=abc')).toEqual({ prop: 'abc' });
43
+ });
44
+
45
+ it('should use parser with unspecified type', () => {
46
+ const parser = searchParams<{ prop: unknown }>({
47
+ prop: () => {
48
+ throw new Error('Just an error');
49
+ },
50
+ });
51
+
52
+ try {
53
+ parser.parse('prop=');
54
+ } catch (e) {
55
+ expect(e).toMatchObject({
56
+ message: 'Unable to parse value',
57
+ cause: {
58
+ message: 'Unable to parse field "prop"',
59
+ cause: {
60
+ message: 'Just an error',
61
+ },
62
+ },
63
+ });
64
+ }
65
+ expect.assertions(1);
66
+ });
67
+
68
+ it('should throw an error in case, passed value contains field of different type presented in schema', () => {
69
+ const parser = searchParams({ prop: date() });
70
+
71
+ try {
72
+ parser.parse('prop=abc');
73
+ } catch (e) {
74
+ expect(e).toMatchObject({
75
+ message: 'Unable to parse value',
76
+ cause: {
77
+ message: 'Unable to parse field "prop" as Date',
78
+ cause: {
79
+ message: 'Unable to parse value as Date',
80
+ cause: {
81
+ message: 'Unable to parse value as number',
82
+ cause: {
83
+ message: 'Value has unexpected type',
84
+ },
85
+ },
86
+ },
87
+ },
88
+ });
89
+ }
90
+ expect.assertions(1);
91
+ });
92
+
93
+ it('should correctly parse built-in types', () => {
94
+ const parser = searchParams({
95
+ date: date(),
96
+ string: string(),
97
+ });
98
+ const params = new URLSearchParams();
99
+ params.set('date', '66653332');
100
+ params.set('string', 'some string');
101
+ expect(parser.parse(params)).toEqual({
102
+ date: new Date(66653332000),
103
+ string: 'some string',
104
+ });
105
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { string } from '../string';
4
+
5
+ it('should return value in case, it has type string', () => {
6
+ expect(string().parse('abc')).toBe('abc');
7
+ });
8
+
9
+ it('should convert value to string in case, it has type number', () => {
10
+ expect(string().parse(1)).toBe('1');
11
+ });
12
+
13
+ it('should throw an error in case, passed value is not string or number', () => {
14
+ expect(() => string().parse({})).toThrow();
15
+ expect(() => string().parse([])).toThrow();
16
+ expect(() => string().parse(false)).toThrow();
17
+ expect(() => string().parse(null)).toThrow();
18
+ expect(() => string().parse(undefined)).toThrow();
19
+ });
20
+
21
+ describe('optional', () => {
22
+ it('should return undefined if value is undefined', () => {
23
+ expect(string().optional().parse(undefined)).toBe(undefined);
24
+ });
25
+ });