@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.
- package/Switch/styles.js +1 -1
- package/package.json +4 -6
- package/Banner/__tests__/Banner.test.d.ts +0 -1
- package/Banner/__tests__/Banner.test.js +0 -57
- package/Button/__tests__/Button.test.d.ts +0 -1
- package/Button/__tests__/Button.test.js +0 -27
- package/Calendar/__tests__/CalendarMonthSlider.test.d.ts +0 -1
- package/Calendar/__tests__/CalendarMonthSlider.test.js +0 -85
- package/Calendar/__tests__/CellBox.test.d.ts +0 -1
- package/Calendar/__tests__/CellBox.test.js +0 -34
- package/Calendar/__tests__/helpers.test.d.ts +0 -1
- package/Calendar/__tests__/helpers.test.js +0 -143
- package/CardMetric/__tests__/CardMetric.test.d.ts +0 -1
- package/CardMetric/__tests__/CardMetric.test.js +0 -31
- package/Chip/__tests__/Chip.test.d.ts +0 -1
- package/Chip/__tests__/Chip.test.js +0 -101
- package/DateTimePicker/__tests__/YearView.test.d.ts +0 -1
- package/DateTimePicker/__tests__/YearView.test.js +0 -15
- package/DropdownMenu/__tests__/DropdownMenuItem.test.d.ts +0 -1
- package/DropdownMenu/__tests__/DropdownMenuItem.test.js +0 -109
- package/EditableField/__tests__/EditableField.test.d.ts +0 -1
- package/EditableField/__tests__/EditableField.test.js +0 -19
- package/EmailTemplatePreview/__tests__/clickMap.test.d.ts +0 -1
- package/EmailTemplatePreview/__tests__/clickMap.test.js +0 -168
- package/ExternalImagePreview/__tests__/ExternalImagePreview.test.d.ts +0 -1
- package/ExternalImagePreview/__tests__/ExternalImagePreview.test.js +0 -18
- package/InboxPreview/__tests__/helpers.test.d.ts +0 -1
- package/InboxPreview/__tests__/helpers.test.js +0 -201
- package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.d.ts +0 -1
- package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.js +0 -81
- package/PageHeader/__tests__/PageHeader.test.d.ts +0 -1
- package/PageHeader/__tests__/PageHeader.test.js +0 -37
- package/PushPreview/__tests__/PushPreview.test.d.ts +0 -1
- package/PushPreview/__tests__/PushPreview.test.js +0 -26
- package/PushStoriesPreview/__tests__/PushStoriesPreview.test.d.ts +0 -1
- package/PushStoriesPreview/__tests__/PushStoriesPreview.test.js +0 -234
- package/RadioCard/__tests__/RadioCard.test.d.ts +0 -1
- package/RadioCard/__tests__/RadioCard.test.js +0 -23
- package/RichmediaPreview/__tests__/RichmediaPreview.test.d.ts +0 -1
- package/RichmediaPreview/__tests__/RichmediaPreview.test.js +0 -21
- package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.d.ts +0 -1
- package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.js +0 -57
- package/Skeleton/__tests__/Skeleton.test.d.ts +0 -1
- package/Skeleton/__tests__/Skeleton.test.js +0 -101
- package/Spinner/__tests__/Spinner.test.d.ts +0 -1
- package/Spinner/__tests__/Spinner.test.js +0 -15
- package/Switch/__tests__/Switch.test.d.ts +0 -1
- package/Switch/__tests__/Switch.test.js +0 -44
- package/Tabs/__tests__/TabBar.test.d.ts +0 -1
- package/Tabs/__tests__/TabBar.test.js +0 -36
- package/Toast/__tests__/Toast.test.d.ts +0 -1
- package/Toast/__tests__/Toast.test.js +0 -20
- package/WhatsAppPreview/__tests__/WhatsAppAttachment.test.d.ts +0 -1
- package/WhatsAppPreview/__tests__/WhatsAppAttachment.test.js +0 -53
- package/WhatsAppPreview/__tests__/WhatsAppPreview.test.d.ts +0 -1
- package/WhatsAppPreview/__tests__/WhatsAppPreview.test.js +0 -55
- package/WhatsAppPreview/__tests__/attachment.test.d.ts +0 -1
- package/WhatsAppPreview/__tests__/attachment.test.js +0 -103
- package/WhatsAppPreview/__tests__/parseWhatsAppMarkup.test.d.ts +0 -1
- package/WhatsAppPreview/__tests__/parseWhatsAppMarkup.test.js +0 -59
- package/helpers/__tests__/barcodeUrl.test.d.ts +0 -1
- package/helpers/__tests__/barcodeUrl.test.js +0 -43
- package/helpers/__tests__/timezones.test.d.ts +0 -1
- package/helpers/__tests__/timezones.test.js +0 -188
- package/hooks/__tests__/useLayer.test.d.ts +0 -1
- package/hooks/__tests__/useLayer.test.js +0 -71
- package/polyglot/__tests__/filters.test.d.ts +0 -1
- package/polyglot/__tests__/filters.test.js +0 -152
- package/polyglot/__tests__/helpers.test.d.ts +0 -1
- package/polyglot/__tests__/helpers.test.js +0 -176
- package/polyglot/__tests__/parser.test.d.ts +0 -1
- package/polyglot/__tests__/parser.test.js +0 -108
- package/polyglot/__tests__/render.test.d.ts +0 -1
- package/polyglot/__tests__/render.test.js +0 -285
- package/shared/dropdownField/__tests__/useDropdownPosition.test.d.ts +0 -1
- package/shared/dropdownField/__tests__/useDropdownPosition.test.js +0 -60
- package/shared/dynamicContent/__tests__/token.test.d.ts +0 -1
- 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 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -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 {};
|