@pushwoosh/dumb-components 2.9.12 → 2.9.14

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/Switch/styles.js +1 -1
  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 -101
  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,55 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render, screen } from '@testing-library/react';
3
- import { WhatsAppPreview } from '../WhatsAppPreview';
4
- describe('WhatsAppPreview', () => {
5
- it('heads the card with the application it sends from', () => {
6
- const {
7
- container
8
- } = render(_jsx(WhatsAppPreview, {
9
- applicationName: "The canary",
10
- message: {
11
- text: 'Hi'
12
- }
13
- }));
14
- expect(screen.getByText('The canary')).toBeInTheDocument();
15
- expect(container.querySelector('svg')).toBeInTheDocument();
16
- });
17
- it('keeps the header on the empty card', () => {
18
- render(_jsx(WhatsAppPreview, {
19
- applicationName: "The canary",
20
- emptyText: "Nothing yet"
21
- }));
22
- expect(screen.getByText('The canary')).toBeInTheDocument();
23
- expect(screen.getByText('Nothing yet')).toBeInTheDocument();
24
- });
25
- it('draws no header without an application', () => {
26
- const {
27
- container
28
- } = render(_jsx(WhatsAppPreview, {
29
- message: {
30
- text: 'Hi'
31
- }
32
- }));
33
- expect(container.querySelector('svg')).not.toBeInTheDocument();
34
- expect(screen.getByText('Hi')).toBeInTheDocument();
35
- });
36
- it('replaces the message with the unavailable-content row', () => {
37
- render(_jsx(WhatsAppPreview, {
38
- isContentMissing: true,
39
- message: {
40
- text: 'Stale promo'
41
- }
42
- }));
43
- expect(screen.getByText('Content missed')).toBeInTheDocument();
44
- expect(screen.getByText('Might be old data or deleted presets')).toBeInTheDocument();
45
- expect(screen.queryByText('Stale promo')).not.toBeInTheDocument();
46
- });
47
- it('keeps the application header above the unavailable-content row', () => {
48
- render(_jsx(WhatsAppPreview, {
49
- applicationName: "The canary",
50
- isContentMissing: true
51
- }));
52
- expect(screen.getByText('The canary')).toBeInTheDocument();
53
- expect(screen.getByText('Content missed')).toBeInTheDocument();
54
- });
55
- });
@@ -1 +0,0 @@
1
- export {};
@@ -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 {};