@pushwoosh/dumb-components 2.9.11 → 2.9.13

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 (78) hide show
  1. package/Skeleton/styles.js +2 -0
  2. package/package.json +4 -6
  3. package/Banner/__tests__/Banner.test.d.ts +0 -1
  4. package/Banner/__tests__/Banner.test.js +0 -57
  5. package/Button/__tests__/Button.test.d.ts +0 -1
  6. package/Button/__tests__/Button.test.js +0 -27
  7. package/Calendar/__tests__/CalendarMonthSlider.test.d.ts +0 -1
  8. package/Calendar/__tests__/CalendarMonthSlider.test.js +0 -85
  9. package/Calendar/__tests__/CellBox.test.d.ts +0 -1
  10. package/Calendar/__tests__/CellBox.test.js +0 -34
  11. package/Calendar/__tests__/helpers.test.d.ts +0 -1
  12. package/Calendar/__tests__/helpers.test.js +0 -143
  13. package/CardMetric/__tests__/CardMetric.test.d.ts +0 -1
  14. package/CardMetric/__tests__/CardMetric.test.js +0 -31
  15. package/Chip/__tests__/Chip.test.d.ts +0 -1
  16. package/Chip/__tests__/Chip.test.js +0 -101
  17. package/DateTimePicker/__tests__/YearView.test.d.ts +0 -1
  18. package/DateTimePicker/__tests__/YearView.test.js +0 -15
  19. package/DropdownMenu/__tests__/DropdownMenuItem.test.d.ts +0 -1
  20. package/DropdownMenu/__tests__/DropdownMenuItem.test.js +0 -109
  21. package/EditableField/__tests__/EditableField.test.d.ts +0 -1
  22. package/EditableField/__tests__/EditableField.test.js +0 -19
  23. package/EmailTemplatePreview/__tests__/clickMap.test.d.ts +0 -1
  24. package/EmailTemplatePreview/__tests__/clickMap.test.js +0 -168
  25. package/ExternalImagePreview/__tests__/ExternalImagePreview.test.d.ts +0 -1
  26. package/ExternalImagePreview/__tests__/ExternalImagePreview.test.js +0 -18
  27. package/InboxPreview/__tests__/helpers.test.d.ts +0 -1
  28. package/InboxPreview/__tests__/helpers.test.js +0 -201
  29. package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.d.ts +0 -1
  30. package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.js +0 -81
  31. package/PageHeader/__tests__/PageHeader.test.d.ts +0 -1
  32. package/PageHeader/__tests__/PageHeader.test.js +0 -37
  33. package/PushPreview/__tests__/PushPreview.test.d.ts +0 -1
  34. package/PushPreview/__tests__/PushPreview.test.js +0 -26
  35. package/PushStoriesPreview/__tests__/PushStoriesPreview.test.d.ts +0 -1
  36. package/PushStoriesPreview/__tests__/PushStoriesPreview.test.js +0 -234
  37. package/RadioCard/__tests__/RadioCard.test.d.ts +0 -1
  38. package/RadioCard/__tests__/RadioCard.test.js +0 -23
  39. package/RichmediaPreview/__tests__/RichmediaPreview.test.d.ts +0 -1
  40. package/RichmediaPreview/__tests__/RichmediaPreview.test.js +0 -21
  41. package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.d.ts +0 -1
  42. package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.js +0 -57
  43. package/Skeleton/__tests__/Skeleton.test.d.ts +0 -1
  44. package/Skeleton/__tests__/Skeleton.test.js +0 -56
  45. package/Spinner/__tests__/Spinner.test.d.ts +0 -1
  46. package/Spinner/__tests__/Spinner.test.js +0 -15
  47. package/Switch/__tests__/Switch.test.d.ts +0 -1
  48. package/Switch/__tests__/Switch.test.js +0 -44
  49. package/Tabs/__tests__/TabBar.test.d.ts +0 -1
  50. package/Tabs/__tests__/TabBar.test.js +0 -36
  51. package/Toast/__tests__/Toast.test.d.ts +0 -1
  52. package/Toast/__tests__/Toast.test.js +0 -20
  53. package/WhatsAppPreview/__tests__/WhatsAppAttachment.test.d.ts +0 -1
  54. package/WhatsAppPreview/__tests__/WhatsAppAttachment.test.js +0 -53
  55. package/WhatsAppPreview/__tests__/WhatsAppPreview.test.d.ts +0 -1
  56. package/WhatsAppPreview/__tests__/WhatsAppPreview.test.js +0 -55
  57. package/WhatsAppPreview/__tests__/attachment.test.d.ts +0 -1
  58. package/WhatsAppPreview/__tests__/attachment.test.js +0 -103
  59. package/WhatsAppPreview/__tests__/parseWhatsAppMarkup.test.d.ts +0 -1
  60. package/WhatsAppPreview/__tests__/parseWhatsAppMarkup.test.js +0 -59
  61. package/helpers/__tests__/barcodeUrl.test.d.ts +0 -1
  62. package/helpers/__tests__/barcodeUrl.test.js +0 -43
  63. package/helpers/__tests__/timezones.test.d.ts +0 -1
  64. package/helpers/__tests__/timezones.test.js +0 -188
  65. package/hooks/__tests__/useLayer.test.d.ts +0 -1
  66. package/hooks/__tests__/useLayer.test.js +0 -71
  67. package/polyglot/__tests__/filters.test.d.ts +0 -1
  68. package/polyglot/__tests__/filters.test.js +0 -152
  69. package/polyglot/__tests__/helpers.test.d.ts +0 -1
  70. package/polyglot/__tests__/helpers.test.js +0 -176
  71. package/polyglot/__tests__/parser.test.d.ts +0 -1
  72. package/polyglot/__tests__/parser.test.js +0 -108
  73. package/polyglot/__tests__/render.test.d.ts +0 -1
  74. package/polyglot/__tests__/render.test.js +0 -285
  75. package/shared/dropdownField/__tests__/useDropdownPosition.test.d.ts +0 -1
  76. package/shared/dropdownField/__tests__/useDropdownPosition.test.js +0 -60
  77. package/shared/dynamicContent/__tests__/token.test.d.ts +0 -1
  78. package/shared/dynamicContent/__tests__/token.test.js +0 -32
@@ -1,103 +0,0 @@
1
- import { formatAttachmentDuration, formatAttachmentSize, getAttachmentExtension, isMessageDrawable, resolveAttachmentKind, toVideoPosterSource } from '../helpers';
2
- const SOURCE = 'data:image/jpeg;base64,AAAA';
3
- describe('resolveAttachmentKind', () => {
4
- it('reads the MIME type first', () => {
5
- expect(resolveAttachmentKind({
6
- name: 'a.pdf',
7
- source: SOURCE,
8
- mimeType: 'image/png'
9
- })).toBe('image');
10
- expect(resolveAttachmentKind({
11
- name: 'a',
12
- source: SOURCE,
13
- mimeType: 'video/mp4'
14
- })).toBe('video');
15
- });
16
- it('falls back to the extension', () => {
17
- expect(resolveAttachmentKind({
18
- name: 'store.JPG',
19
- source: SOURCE
20
- })).toBe('image');
21
- expect(resolveAttachmentKind({
22
- name: 'tour.mp4',
23
- source: SOURCE
24
- })).toBe('video');
25
- expect(resolveAttachmentKind({
26
- name: 'terms.docx',
27
- source: SOURCE
28
- })).toBe('document');
29
- });
30
- it('treats an unknown file as a document', () => {
31
- expect(resolveAttachmentKind({
32
- name: 'notes',
33
- source: SOURCE
34
- })).toBe('document');
35
- });
36
- it('obeys an explicit kind', () => {
37
- expect(resolveAttachmentKind({
38
- name: 'a.png',
39
- source: SOURCE,
40
- kind: 'document',
41
- mimeType: 'image/png'
42
- })).toBe('document');
43
- });
44
- });
45
- describe('attachment meta', () => {
46
- it('reads the extension off the name', () => {
47
- expect(getAttachmentExtension({
48
- name: 'Price list.pdf',
49
- source: SOURCE
50
- })).toBe('pdf');
51
- expect(getAttachmentExtension({
52
- name: 'notes',
53
- source: SOURCE
54
- })).toBe('');
55
- });
56
- it('falls back to the MIME subtype when the name has no extension', () => {
57
- expect(getAttachmentExtension({
58
- name: 'Title',
59
- source: SOURCE,
60
- mimeType: 'application/PDF'
61
- })).toBe('pdf');
62
- expect(getAttachmentExtension({
63
- name: 'Title.docx',
64
- source: SOURCE,
65
- mimeType: 'application/pdf'
66
- })).toBe('docx');
67
- });
68
- it('writes sizes the way WhatsApp does', () => {
69
- expect(formatAttachmentSize(912)).toBe('912 B');
70
- expect(formatAttachmentSize(69_632)).toBe('68 kB');
71
- expect(formatAttachmentSize(1_468_006)).toBe('1.4 MB');
72
- });
73
- it('writes video length as m:ss', () => {
74
- expect(formatAttachmentDuration(6.2)).toBe('0:06');
75
- expect(formatAttachmentDuration(75)).toBe('1:15');
76
- });
77
- it('asks the video for a poster frame once', () => {
78
- expect(toVideoPosterSource('blob:tour')).toBe('blob:tour#t=0.001');
79
- expect(toVideoPosterSource('blob:tour#t=2')).toBe('blob:tour#t=2');
80
- });
81
- });
82
- describe('isMessageDrawable', () => {
83
- it('keeps a message that has a body or a file', () => {
84
- expect(isMessageDrawable({
85
- text: 'hi'
86
- })).toBe(true);
87
- expect(isMessageDrawable({
88
- attachments: [{
89
- name: 'a.jpg',
90
- source: SOURCE
91
- }]
92
- })).toBe(true);
93
- });
94
- it('drops a message with nothing to show', () => {
95
- expect(isMessageDrawable({})).toBe(false);
96
- expect(isMessageDrawable({
97
- attachments: [{
98
- name: 'a.jpg',
99
- source: ''
100
- }]
101
- })).toBe(false);
102
- });
103
- });
@@ -1,59 +0,0 @@
1
- import { parseWhatsAppMarkup } from '../helpers';
2
- describe('parseWhatsAppMarkup', () => {
3
- it('keeps plain text as a single node', () => {
4
- expect(parseWhatsAppMarkup('Hi there')).toEqual([{
5
- text: 'Hi there'
6
- }]);
7
- });
8
- it('marks bold, italic and strikethrough runs', () => {
9
- expect(parseWhatsAppMarkup('*a* _b_ ~c~')).toEqual([{
10
- style: 'bold',
11
- children: [{
12
- text: 'a'
13
- }]
14
- }, {
15
- text: ' '
16
- }, {
17
- style: 'italic',
18
- children: [{
19
- text: 'b'
20
- }]
21
- }, {
22
- text: ' '
23
- }, {
24
- style: 'strike',
25
- children: [{
26
- text: 'c'
27
- }]
28
- }]);
29
- });
30
- it('nests styles', () => {
31
- expect(parseWhatsAppMarkup('*bold _both_*')).toEqual([{
32
- style: 'bold',
33
- children: [{
34
- text: 'bold '
35
- }, {
36
- style: 'italic',
37
- children: [{
38
- text: 'both'
39
- }]
40
- }]
41
- }]);
42
- });
43
- it('keeps monospace content literal', () => {
44
- expect(parseWhatsAppMarkup('```*raw*```')).toEqual([{
45
- style: 'mono',
46
- children: [{
47
- text: '*raw*'
48
- }]
49
- }]);
50
- });
51
- it('leaves unpaired and empty markers as text', () => {
52
- expect(parseWhatsAppMarkup('2 * 3 = 6')).toEqual([{
53
- text: '2 * 3 = 6'
54
- }]);
55
- expect(parseWhatsAppMarkup('**')).toEqual([{
56
- text: '**'
57
- }]);
58
- });
59
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- import { buildBarcodeUrl, barcodePlaceholderSafeEscape, isBarcodePlaceholder } from '../barcodeUrl';
2
- const BASE = 'https://rag-mcp.svc-nue.pushwoosh.com';
3
- describe('buildBarcodeUrl', () => {
4
- it('omits every empty parameter', () => {
5
- expect(buildBarcodeUrl({
6
- value: '1234567890123456'
7
- }, `${BASE}/`)).toBe(`${BASE}/barcode/v1/code.png?sym=code128&v=1234567890123456`);
8
- });
9
- // A percent-encoded brace never matches the substituter, on the server or on
10
- // the device, so the barcode would encode the placeholder text itself.
11
- it.each(['{{voucher}}', '{Coupon|String|}', 'BR-{{voucher}}'])('keeps the placeholder %s literal', value => {
12
- expect(buildBarcodeUrl({
13
- value
14
- }, BASE).endsWith(`&v=${value}`)).toBe(true);
15
- });
16
- it('escapes what is not a placeholder', () => {
17
- expect(barcodePlaceholderSafeEscape('a b&c={{v}}')).toBe('a+b%26c%3D{{v}}');
18
- });
19
- it('carries every set parameter', () => {
20
- const url = buildBarcodeUrl({
21
- value: '{Coupon|String|}',
22
- symbology: 'qr',
23
- showText: true,
24
- width: 420,
25
- height: 460,
26
- quietModules: 6,
27
- foregroundColor: '#101010',
28
- backgroundColor: '#ffffff',
29
- ecLevel: 'H'
30
- }, BASE);
31
- ['sym=qr', 'hri=1', 'w=420', 'h=460', 'q=6', 'fg=101010', 'bg=ffffff', 'ec=H'].forEach(part => expect(url).toContain(part));
32
- });
33
- it('drops the error-correction level outside QR', () => {
34
- expect(buildBarcodeUrl({
35
- value: '1',
36
- ecLevel: 'H'
37
- }, BASE)).not.toContain('ec=');
38
- });
39
- it('recognizes a value that still needs substituting', () => {
40
- expect(isBarcodePlaceholder('{Coupon|String|}')).toBe(true);
41
- expect(isBarcodePlaceholder('4012345678901')).toBe(false);
42
- });
43
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,188 +0,0 @@
1
- import { canonicalizeTimezone, findTimezoneOption, formatTimezoneLabel, formatTimezoneOffset, formatTimezoneAbbreviation, formatTimezoneBadge, formatTimezoneShort, getTimezoneOffsetSafe, listTimezones, resolveTimezoneOption } from '../timezones';
2
- const winter = new Date('2026-01-15T12:00:00Z');
3
- const melbourneDst = new Date('2026-10-10T12:00:00Z');
4
- const melbourneStandard = new Date('2026-06-15T12:00:00Z');
5
- describe('formatTimezoneOffset', () => {
6
- it('renders sign, hours and minutes', () => {
7
- expect(formatTimezoneOffset(300)).toBe('UTC+05:00');
8
- expect(formatTimezoneOffset(330)).toBe('UTC+05:30');
9
- expect(formatTimezoneOffset(-210)).toBe('UTC-03:30');
10
- expect(formatTimezoneOffset(0)).toBe('UTC+00:00');
11
- });
12
- });
13
- describe('formatTimezoneShort', () => {
14
- it('renders the bare offset the calendar badge shows', () => {
15
- expect(formatTimezoneShort('Asia/Almaty', winter)).toBe('UTC+05:00');
16
- expect(formatTimezoneShort('Asia/Kolkata', winter)).toBe('UTC+05:30');
17
- expect(formatTimezoneShort('UTC', winter)).toBe('UTC+00:00');
18
- });
19
- it('follows DST at the given instant', () => {
20
- expect(formatTimezoneShort('Australia/Melbourne', melbourneStandard)).toBe('UTC+10:00');
21
- expect(formatTimezoneShort('Australia/Melbourne', melbourneDst)).toBe('UTC+11:00');
22
- });
23
- it('keeps an unknown zone readable', () => {
24
- expect(formatTimezoneShort('Not/AZone', winter)).toBe('Not/AZone');
25
- });
26
- });
27
- describe('formatTimezoneAbbreviation', () => {
28
- it('returns the letter abbreviation where the engine has one', () => {
29
- expect(formatTimezoneAbbreviation('America/New_York', winter)).toBe('EST');
30
- expect(formatTimezoneAbbreviation('America/Los_Angeles', winter)).toBe('PST');
31
- });
32
- it('returns null where the engine only offers a GMT-style name', () => {
33
- expect(formatTimezoneAbbreviation('Asia/Karachi', winter)).toBeNull();
34
- expect(formatTimezoneAbbreviation('Asia/Kolkata', winter)).toBeNull();
35
- expect(formatTimezoneAbbreviation('Europe/Moscow', winter)).toBeNull();
36
- });
37
- it('returns null for UTC and for a zone it does not know', () => {
38
- expect(formatTimezoneAbbreviation('UTC', winter)).toBeNull();
39
- expect(formatTimezoneAbbreviation('Not/AZone', winter)).toBeNull();
40
- });
41
- it('follows DST at the given instant', () => {
42
- expect(formatTimezoneAbbreviation('America/New_York', new Date('2026-07-01T12:00:00Z'))).toBe('EDT');
43
- });
44
- });
45
- describe('formatTimezoneBadge', () => {
46
- it('pairs the abbreviation with the offset', () => {
47
- expect(formatTimezoneBadge('America/New_York', winter)).toBe('EST (UTC-05:00)');
48
- });
49
- it('falls back to the bare offset for a zone without an abbreviation', () => {
50
- expect(formatTimezoneBadge('Asia/Karachi', winter)).toBe('UTC+05:00');
51
- expect(formatTimezoneBadge('Asia/Kolkata', winter)).toBe('UTC+05:30');
52
- expect(formatTimezoneBadge('UTC', winter)).toBe('UTC+00:00');
53
- });
54
- it('follows DST at the given instant', () => {
55
- expect(formatTimezoneBadge('Australia/Melbourne', melbourneStandard)).toBe('UTC+10:00');
56
- expect(formatTimezoneBadge('Australia/Melbourne', melbourneDst)).toBe('UTC+11:00');
57
- });
58
- it('keeps an unknown zone readable', () => {
59
- expect(formatTimezoneBadge('Not/AZone', winter)).toBe('Not/AZone');
60
- });
61
- });
62
- describe('formatTimezoneLabel', () => {
63
- it('uses the current tzdata: Kazakhstan is UTC+5 since 2024', () => {
64
- expect(formatTimezoneLabel('Asia/Almaty', winter)).toBe('(UTC+05:00) Asia/Almaty');
65
- expect(formatTimezoneLabel('Asia/Qostanay', winter)).toBe('(UTC+05:00) Asia/Qostanay');
66
- });
67
- it('keeps half-hour zones east of UTC exact', () => {
68
- expect(formatTimezoneLabel('Asia/Kolkata', winter)).toBe('(UTC+05:30) Asia/Kolkata');
69
- expect(formatTimezoneLabel('Asia/Kathmandu', winter)).toBe('(UTC+05:45) Asia/Kathmandu');
70
- expect(formatTimezoneLabel('America/St_Johns', winter)).toBe('(UTC-03:30) America/St_Johns');
71
- });
72
- it('resolves the offset at the given instant, not today', () => {
73
- expect(getTimezoneOffsetSafe('Australia/Melbourne', winter)).toBe(660);
74
- expect(getTimezoneOffsetSafe('Australia/Melbourne', new Date('2026-07-01T00:00:00Z'))).toBe(600);
75
- expect(getTimezoneOffsetSafe('Australia/Melbourne', melbourneDst)).toBe(660);
76
- });
77
- it('labels Etc and POSIX names the backend accepts but the picker does not list', () => {
78
- expect(formatTimezoneLabel('Etc/GMT+3', winter)).toBe('(UTC-03:00) Etc/GMT+3');
79
- expect(formatTimezoneLabel('Etc/GMT-0', winter)).toBe('(UTC+00:00) Etc/GMT-0');
80
- expect(formatTimezoneLabel('EST', winter)).toBe('(UTC-05:00) EST');
81
- });
82
- it('falls back to the bare name for a zone the engine does not know', () => {
83
- expect(getTimezoneOffsetSafe('Not/A_Zone')).toBeNull();
84
- expect(formatTimezoneLabel('Not/A_Zone')).toBe('Not/A_Zone');
85
- });
86
- });
87
- describe('canonicalizeTimezone', () => {
88
- it('maps aliases onto one engine name and rejects unknown zones', () => {
89
- expect(canonicalizeTimezone('Europe/Kiev')).toBe(canonicalizeTimezone('Europe/Kyiv'));
90
- expect(canonicalizeTimezone('Asia/Calcutta')).toBe(canonicalizeTimezone('Asia/Kolkata'));
91
- expect(canonicalizeTimezone('Etc/UTC')).toBe('UTC');
92
- expect(canonicalizeTimezone('Not/A_Zone')).toBeNull();
93
- });
94
- });
95
- describe('listTimezones', () => {
96
- const options = listTimezones(winter);
97
- it('contains UTC exactly once and no duplicates', () => {
98
- expect(options.filter(option => option.value === 'UTC')).toHaveLength(1);
99
- expect(new Set(options.map(option => option.value)).size).toBe(options.length);
100
- });
101
- it('covers every zone the engine reports', () => {
102
- const supported = Intl.supportedValuesOf('timeZone');
103
- expect(options.length).toBeGreaterThanOrEqual(supported.length);
104
- supported.forEach(name => {
105
- expect(options.some(option => option.value === name)).toBe(true);
106
- });
107
- });
108
- it('is sorted by offset, UTC first among the zero-offset zones, then by name', () => {
109
- const offsets = options.map(option => getTimezoneOffsetSafe(option.value, winter) ?? Infinity);
110
- expect([...offsets].sort((a, b) => a - b)).toEqual(offsets);
111
- const zeroOffset = options.filter(option => getTimezoneOffsetSafe(option.value, winter) === 0);
112
- expect(zeroOffset[0].value).toBe('UTC');
113
- });
114
- it('labels every option in the shared format', () => {
115
- options.forEach(option => {
116
- expect(option.label).toBe(formatTimezoneLabel(option.value, winter));
117
- });
118
- });
119
- it('keeps stored values selectable verbatim, skipping blanks', () => {
120
- const withStored = listTimezones(winter, ['Asia/Calcutta', 'Etc/GMT-0', 'EST', 'Not/A_Zone', '', null, undefined]);
121
- ['Asia/Calcutta', 'Etc/GMT-0', 'EST', 'Not/A_Zone'].forEach(name => {
122
- expect(withStored.filter(option => option.value === name)).toHaveLength(1);
123
- });
124
- expect(withStored.some(option => option.value === '')).toBe(false);
125
- expect(withStored[withStored.length - 1].value).toBe('Not/A_Zone');
126
- });
127
- it('answers an equal but fresh list when called again for the same minute', () => {
128
- const again = listTimezones(winter);
129
- expect(again).toEqual(options);
130
- expect(again).not.toBe(options);
131
- again.length = 0;
132
- expect(listTimezones(winter)).toEqual(options);
133
- });
134
- it('re-resolves offsets when the instant moves across a DST boundary', () => {
135
- const standard = listTimezones(melbourneStandard).find(option => option.value === 'Australia/Melbourne');
136
- const dst = listTimezones(melbourneDst).find(option => option.value === 'Australia/Melbourne');
137
- expect(standard?.label).toBe('(UTC+10:00) Australia/Melbourne');
138
- expect(dst?.label).toBe('(UTC+11:00) Australia/Melbourne');
139
- });
140
- it('falls back to the browser zone and UTC when the engine has no zone list', () => {
141
- const original = Intl.supportedValuesOf;
142
- Object.defineProperty(Intl, 'supportedValuesOf', {
143
- value: undefined,
144
- configurable: true
145
- });
146
- try {
147
- const fallback = listTimezones(winter).map(option => option.value);
148
- expect(fallback).toContain('UTC');
149
- expect(fallback).toContain(Intl.DateTimeFormat().resolvedOptions().timeZone);
150
- expect(fallback.length).toBeLessThanOrEqual(2);
151
- } finally {
152
- Object.defineProperty(Intl, 'supportedValuesOf', {
153
- value: original,
154
- configurable: true
155
- });
156
- }
157
- });
158
- });
159
- describe('findTimezoneOption', () => {
160
- const options = listTimezones(winter, ['Asia/Calcutta']);
161
- it('matches a stored name exactly, aliases included', () => {
162
- expect(findTimezoneOption(options, 'Asia/Almaty', winter)?.value).toBe('Asia/Almaty');
163
- expect(findTimezoneOption(options, 'Asia/Calcutta', winter)?.value).toBe('Asia/Calcutta');
164
- expect(findTimezoneOption(options, 'Not/Stored', winter)).toBeNull();
165
- });
166
- it('maps a legacy numeric offset onto a zone with that offset at the instant', () => {
167
- expect(getTimezoneOffsetSafe(findTimezoneOption(options, 330, winter).value, winter)).toBe(330);
168
- expect(getTimezoneOffsetSafe(findTimezoneOption(options, '-210', winter).value, winter)).toBe(-210);
169
- expect(findTimezoneOption(options, 7, winter)).toBeNull();
170
- });
171
- it('returns null for an empty value', () => {
172
- expect(findTimezoneOption(options, '', winter)).toBeNull();
173
- expect(findTimezoneOption(options, null, winter)).toBeNull();
174
- expect(findTimezoneOption(options, undefined, winter)).toBeNull();
175
- });
176
- });
177
- describe('resolveTimezoneOption', () => {
178
- it('never rewrites the stored value', () => {
179
- expect(resolveTimezoneOption('Europe/Kiev', winter)).toEqual({
180
- value: 'Europe/Kiev',
181
- label: '(UTC+02:00) Europe/Kiev'
182
- });
183
- expect(resolveTimezoneOption('Not/A_Zone', winter)).toEqual({
184
- value: 'Not/A_Zone',
185
- label: 'Not/A_Zone'
186
- });
187
- });
188
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,71 +0,0 @@
1
- import { render } from '@testing-library/react';
2
- import { createElement, Fragment } from 'react';
3
- import { useLayer } from '../useLayer';
4
- const GLOBAL_KEY = '__pushwoosh_dumb_components_layer_state__';
5
- function resetLayerState() {
6
- delete window[GLOBAL_KEY];
7
- }
8
- function Layer({
9
- index,
10
- zIndex
11
- }) {
12
- const resolved = useLayer({
13
- isOpen: true,
14
- zIndex
15
- });
16
- return createElement('div', {
17
- 'data-testid': `layer-${index}`,
18
- 'data-z': resolved.zIndex
19
- });
20
- }
21
- function layersTree(zIndexes) {
22
- const children = zIndexes.map((zIndex, index) => createElement(Layer, {
23
- // eslint-disable-next-line react/no-array-index-key
24
- key: index,
25
- index,
26
- zIndex
27
- }));
28
- return createElement(Fragment, null, ...children);
29
- }
30
- function readZIndexes(getByTestId, count) {
31
- return Array.from({
32
- length: count
33
- }, (_, index) => Number(getByTestId(`layer-${index}`).getAttribute('data-z')));
34
- }
35
- function renderZIndexes(zIndexes) {
36
- const {
37
- getByTestId
38
- } = render(layersTree(zIndexes));
39
- return readZIndexes(getByTestId, zIndexes.length);
40
- }
41
- describe('useLayer z-index resolution', () => {
42
- beforeEach(resetLayerState);
43
- afterEach(resetLayerState);
44
- it('puts a lone layer at the base z-index', () => {
45
- expect(renderZIndexes([undefined])).toEqual([10000]);
46
- });
47
- it('stacks plain layers in registration order', () => {
48
- expect(renderZIndexes([undefined, undefined])).toEqual([10000, 10100]);
49
- });
50
- it('keeps a layer opened on top of an explicit base above it', () => {
51
- expect(renderZIndexes([100000, undefined])).toEqual([100000, 100100]);
52
- });
53
- it('propagates the base to every layer stacked above it', () => {
54
- expect(renderZIndexes([100000, undefined, undefined])).toEqual([100000, 100100, 100200]);
55
- });
56
- it('renders a lone explicit base exactly at its value', () => {
57
- expect(renderZIndexes([100000])).toEqual([100000]);
58
- });
59
- it('honors a higher base set by a layer above', () => {
60
- expect(renderZIndexes([undefined, 999999])).toEqual([10000, 999999]);
61
- });
62
- it('re-resolves stacked layers when an open layer changes its base', () => {
63
- const {
64
- rerender,
65
- getByTestId
66
- } = render(layersTree([100000, undefined]));
67
- expect(readZIndexes(getByTestId, 2)).toEqual([100000, 100100]);
68
- rerender(layersTree([200000, undefined]));
69
- expect(readZIndexes(getByTestId, 2)).toEqual([200000, 200100]);
70
- });
71
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,152 +0,0 @@
1
- import { filters } from '../liquid/filters';
2
- const createContext = (language = 'en', timezone = 'UTC') => ({
3
- values: {},
4
- language,
5
- timezone
6
- });
7
- const scalar = value => ({
8
- type: 'Scalar',
9
- value
10
- });
11
- describe('filters', () => {
12
- describe('capitalize', () => {
13
- test('capitalizes first letter', () => {
14
- expect(filters.capitalize('hello', [], createContext())).toBe('Hello');
15
- });
16
- test('handles empty string', () => {
17
- expect(filters.capitalize('', [], createContext())).toBe('');
18
- });
19
- test('returns non-string as is', () => {
20
- expect(filters.capitalize(42, [], createContext())).toBe(42);
21
- });
22
- });
23
- describe('upperCase', () => {
24
- test('converts to uppercase', () => {
25
- expect(filters.upperCase('hello', [], createContext())).toBe('HELLO');
26
- });
27
- test('returns non-string as is', () => {
28
- expect(filters.upperCase(42, [], createContext())).toBe(42);
29
- });
30
- });
31
- describe('lowerCase', () => {
32
- test('converts to lowercase', () => {
33
- expect(filters.lowerCase('HELLO', [], createContext())).toBe('hello');
34
- });
35
- test('returns non-string as is', () => {
36
- expect(filters.lowerCase(42, [], createContext())).toBe(42);
37
- });
38
- });
39
- describe('pluralize', () => {
40
- describe('English', () => {
41
- const ctx = createContext('en');
42
- test('returns singular for 1', () => {
43
- expect(filters.pluralize(1, [scalar('item'), scalar('items')], ctx)).toBe('item');
44
- });
45
- test('returns plural for 0', () => {
46
- expect(filters.pluralize(0, [scalar('item'), scalar('items')], ctx)).toBe('items');
47
- });
48
- test('returns plural for 2+', () => {
49
- expect(filters.pluralize(5, [scalar('item'), scalar('items')], ctx)).toBe('items');
50
- });
51
- });
52
- describe('Russian', () => {
53
- const ctx = createContext('ru');
54
- test('returns first form for 1', () => {
55
- expect(filters.pluralize(1, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблоко');
56
- });
57
- test('returns second form for 2-4', () => {
58
- expect(filters.pluralize(2, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблока');
59
- expect(filters.pluralize(3, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблока');
60
- expect(filters.pluralize(4, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблока');
61
- });
62
- test('returns third form for 5-20', () => {
63
- expect(filters.pluralize(5, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблок');
64
- expect(filters.pluralize(11, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблок');
65
- expect(filters.pluralize(20, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблок');
66
- });
67
- test('returns first form for 21', () => {
68
- expect(filters.pluralize(21, [scalar('яблоко'), scalar('яблока'), scalar('яблок')], ctx)).toBe('яблоко');
69
- });
70
- });
71
- test('returns value as is for non-number', () => {
72
- expect(filters.pluralize('not a number', [scalar('a'), scalar('b')], createContext())).toBe('not a number');
73
- });
74
- });
75
- describe('pluralize with CLDR forms', () => {
76
- const arabic = ['صفر', 'صف', 'صفان', 'صفوف', 'صفًا', 'صف*'].map(scalar);
77
- test('picks each of the six Arabic forms', () => {
78
- const ctx = createContext('ar');
79
- expect(filters.pluralize(0, arabic, ctx)).toBe('صفر');
80
- expect(filters.pluralize(1, arabic, ctx)).toBe('صف');
81
- expect(filters.pluralize(2, arabic, ctx)).toBe('صفان');
82
- expect(filters.pluralize(3, arabic, ctx)).toBe('صفوف');
83
- expect(filters.pluralize(10, arabic, ctx)).toBe('صفوف');
84
- expect(filters.pluralize(11, arabic, ctx)).toBe('صفًا');
85
- expect(filters.pluralize(99, arabic, ctx)).toBe('صفًا');
86
- expect(filters.pluralize(100, arabic, ctx)).toBe('صف*');
87
- expect(filters.pluralize(103, arabic, ctx)).toBe('صفوف');
88
- });
89
- test('keeps the legacy one/other split when fewer forms are given', () => {
90
- const ctx = createContext('ar');
91
- expect(filters.pluralize(1, [scalar('صف'), scalar('صفوف')], ctx)).toBe('صف');
92
- expect(filters.pluralize(0, [scalar('صف'), scalar('صفوف')], ctx)).toBe('صفوف');
93
- expect(filters.pluralize(2, [scalar('صف'), scalar('صفوف')], ctx)).toBe('صفوف');
94
- });
95
- test('Russian three-form rule matches CLDR one/few/many for integers', () => {
96
- const ctx = createContext('ru');
97
- const forms = [scalar('one'), scalar('few'), scalar('many')];
98
- const rules = new Intl.PluralRules('ru');
99
- for (let n = 0; n <= 1000; n += 1) {
100
- expect(filters.pluralize(n, forms, ctx)).toBe(rules.select(n));
101
- }
102
- });
103
- });
104
- describe('formatNumber', () => {
105
- test('formats integer with spaces', () => {
106
- expect(filters.formatNumber('1000000', [], createContext())).toBe('1 000 000');
107
- });
108
- test('formats with decimal places', () => {
109
- expect(filters.formatNumber('1234.5', [scalar(2)], createContext())).toBe('1 234,50');
110
- });
111
- test('truncates excess decimal places', () => {
112
- expect(filters.formatNumber('1234.5678', [scalar(2)], createContext())).toBe('1 234,56');
113
- });
114
- test('handles integer without decimal', () => {
115
- expect(filters.formatNumber('1234', [], createContext())).toBe('1 234');
116
- });
117
- });
118
- describe('formatDate', () => {
119
- const testDate = '2024-06-15T12:30:00Z';
120
- test('formats date with context timezone', () => {
121
- const result = filters.formatDate(testDate, [], createContext('en', 'UTC'));
122
- expect(result).toBe('Jun 15, 2024');
123
- });
124
- test('formats date with short format', () => {
125
- const result = filters.formatDate(testDate, [scalar('short')], createContext('en', 'UTC'));
126
- expect(result).toMatch(/\d{1,2}\/\d{1,2}\/\d{2,4}/);
127
- });
128
- test('formats date with long format', () => {
129
- const result = filters.formatDate(testDate, [scalar('long')], createContext('en', 'UTC'));
130
- expect(result).toContain('June');
131
- expect(result).toContain('2024');
132
- });
133
- test('formats date with different locale', () => {
134
- const result = filters.formatDate(testDate, [], createContext('ru', 'UTC'));
135
- expect(result).toContain('2024');
136
- });
137
- });
138
- describe('formatDateTime', () => {
139
- const testDate = '2024-06-15T12:30:00Z';
140
- test('formats date and time with context timezone', () => {
141
- const result = filters.formatDateTime(testDate, [], createContext('en', 'UTC'));
142
- expect(result).toContain('Jun');
143
- expect(result).toContain('2024');
144
- expect(result).toContain('12:30');
145
- });
146
- test('formats date and time with short format', () => {
147
- const result = filters.formatDateTime(testDate, [scalar('short')], createContext('en', 'UTC'));
148
- expect(result).toMatch(/\d{1,2}\/\d{1,2}\/\d{2,4}/);
149
- expect(result).toContain(':');
150
- });
151
- });
152
- });
@@ -1 +0,0 @@
1
- export {};