@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.
- package/Skeleton/styles.js +2 -0
- 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 -56
- 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 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { Color } from '@pushwoosh/kit-constants';
|
|
4
|
-
import { EditableField } from '../EditableField';
|
|
5
|
-
describe('EditableField', () => {
|
|
6
|
-
it('paints the open field without an `open` attribute on the div', () => {
|
|
7
|
-
render(_jsx(EditableField, {
|
|
8
|
-
value: "Campaign",
|
|
9
|
-
open: true,
|
|
10
|
-
onToggleOpen: jest.fn(),
|
|
11
|
-
onSubmit: jest.fn(),
|
|
12
|
-
onCancel: jest.fn(),
|
|
13
|
-
children: "Editor"
|
|
14
|
-
}));
|
|
15
|
-
const field = screen.getByText('Campaign').closest('div');
|
|
16
|
-
expect(field).not.toHaveAttribute('open');
|
|
17
|
-
expect(getComputedStyle(field).background.replace(/\s/g, '')).toBe(Color.MAIN.replace(/\s/g, ''));
|
|
18
|
-
});
|
|
19
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { CLICK_MAP_BADGE_ATTRIBUTE, CLICK_MAP_HEAT_ATTRIBUTE, decorateClickMap, matchClickMapItems } from '../clickMap';
|
|
2
|
-
const PROMO = 'http://spartak.com/matchtowin';
|
|
3
|
-
const TICKETS = 'https://tickets.spartak.com/zones/18956';
|
|
4
|
-
const SHOP = 'https://shop.spartak.com/';
|
|
5
|
-
const HTML = `
|
|
6
|
-
<a href="${PROMO}?utm_source=email&utm_content=img"><img src="banner.png" alt=""></a>
|
|
7
|
-
<p>Подробности на <a href="${PROMO}?utm_source=email">странице акции</a>.</p>
|
|
8
|
-
<a href="${PROMO}?utm_source=email"><strong>Перейти на страницу акции</strong></a>
|
|
9
|
-
<a href="${TICKETS}?utm_source=email">Купить билеты</a>
|
|
10
|
-
<a href="${TICKETS}?utm_source=email">Купить билеты</a>
|
|
11
|
-
<a href="[% param.url_unsub %]">Отписаться</a>
|
|
12
|
-
<a data-pw-lid="aaaa11112222" href="${SHOP}">В магазин</a>
|
|
13
|
-
<a data-pw-lid="bbbb33334444" href="${SHOP}">В магазин</a>
|
|
14
|
-
`;
|
|
15
|
-
const item = (key, link, title, clicks, linkId) => ({
|
|
16
|
-
key,
|
|
17
|
-
link,
|
|
18
|
-
title,
|
|
19
|
-
clicks,
|
|
20
|
-
linkId
|
|
21
|
-
});
|
|
22
|
-
function createDocument() {
|
|
23
|
-
const frameDocument = document.implementation.createHTMLDocument('preview');
|
|
24
|
-
frameDocument.body.innerHTML = HTML;
|
|
25
|
-
return frameDocument;
|
|
26
|
-
}
|
|
27
|
-
describe('matchClickMapItems', () => {
|
|
28
|
-
it('tells anchors with the same link apart by their text and lands image links on the text-less anchor', () => {
|
|
29
|
-
const frameDocument = createDocument();
|
|
30
|
-
const items = [item('button', PROMO, 'Перейти на страницу акции', 1631), item('text', PROMO, 'странице акции', 398), item('image', PROMO, '', 172)];
|
|
31
|
-
const {
|
|
32
|
-
matches,
|
|
33
|
-
placement
|
|
34
|
-
} = matchClickMapItems(frameDocument, items);
|
|
35
|
-
expect(placement).toEqual({
|
|
36
|
-
placed: ['button', 'text', 'image'],
|
|
37
|
-
pooled: [],
|
|
38
|
-
unplaced: []
|
|
39
|
-
});
|
|
40
|
-
expect(matches.map(({
|
|
41
|
-
anchor,
|
|
42
|
-
items: matched
|
|
43
|
-
}) => [anchor.textContent?.trim(), matched.map(row => row.key)])).toEqual([['Перейти на страницу акции', ['button']], ['странице акции', ['text']], ['', ['image']]]);
|
|
44
|
-
});
|
|
45
|
-
it('ignores the query string of both sides when matching links', () => {
|
|
46
|
-
const {
|
|
47
|
-
placement
|
|
48
|
-
} = matchClickMapItems(createDocument(), [item('text', `${PROMO}?utm_source=other&x=1`, 'странице акции', 1)]);
|
|
49
|
-
expect(placement.placed).toEqual(['text']);
|
|
50
|
-
});
|
|
51
|
-
it('pools a row over every anchor that shares link and text', () => {
|
|
52
|
-
const {
|
|
53
|
-
matches,
|
|
54
|
-
placement
|
|
55
|
-
} = matchClickMapItems(createDocument(), [item('tickets', TICKETS, 'Купить билеты', 258)]);
|
|
56
|
-
expect(placement).toEqual({
|
|
57
|
-
placed: [],
|
|
58
|
-
pooled: ['tickets'],
|
|
59
|
-
unplaced: []
|
|
60
|
-
});
|
|
61
|
-
expect(matches).toHaveLength(2);
|
|
62
|
-
});
|
|
63
|
-
it('falls back to every anchor of the link when the recorded text matches none of them', () => {
|
|
64
|
-
const {
|
|
65
|
-
matches,
|
|
66
|
-
placement
|
|
67
|
-
} = matchClickMapItems(createDocument(), [item('promo', PROMO, 'Use this template →', 10)]);
|
|
68
|
-
expect(placement.pooled).toEqual(['promo']);
|
|
69
|
-
expect(matches).toHaveLength(3);
|
|
70
|
-
});
|
|
71
|
-
it('tells identical anchors apart by the stamped link id', () => {
|
|
72
|
-
const {
|
|
73
|
-
matches,
|
|
74
|
-
placement
|
|
75
|
-
} = matchClickMapItems(createDocument(), [item('top', SHOP, 'В магазин', 40, 'aaaa11112222'), item('bottom', SHOP, 'В магазин', 9, 'bbbb33334444')]);
|
|
76
|
-
expect(placement).toEqual({
|
|
77
|
-
placed: ['top', 'bottom'],
|
|
78
|
-
pooled: [],
|
|
79
|
-
unplaced: []
|
|
80
|
-
});
|
|
81
|
-
expect(matches.map(({
|
|
82
|
-
anchor,
|
|
83
|
-
items: matched
|
|
84
|
-
}) => [anchor.getAttribute('data-pw-lid'), matched.map(row => row.key)])).toEqual([['aaaa11112222', ['top']], ['bbbb33334444', ['bottom']]]);
|
|
85
|
-
});
|
|
86
|
-
it('falls back to link and text when the template no longer carries the recorded link id', () => {
|
|
87
|
-
const {
|
|
88
|
-
matches,
|
|
89
|
-
placement
|
|
90
|
-
} = matchClickMapItems(createDocument(), [item('old', SHOP, 'В магазин', 3, 'cccc55556666'), item('unstamped', PROMO, 'странице акции', 1)]);
|
|
91
|
-
expect(placement).toEqual({
|
|
92
|
-
placed: ['unstamped'],
|
|
93
|
-
pooled: ['old'],
|
|
94
|
-
unplaced: []
|
|
95
|
-
});
|
|
96
|
-
expect(matches).toHaveLength(3);
|
|
97
|
-
});
|
|
98
|
-
it('reports rows whose link is not in the template as unplaced', () => {
|
|
99
|
-
const {
|
|
100
|
-
matches,
|
|
101
|
-
placement
|
|
102
|
-
} = matchClickMapItems(createDocument(), [item('gone', 'https://old.example.com/', 'x', 5)]);
|
|
103
|
-
expect(placement).toEqual({
|
|
104
|
-
placed: [],
|
|
105
|
-
pooled: [],
|
|
106
|
-
unplaced: ['gone']
|
|
107
|
-
});
|
|
108
|
-
expect(matches).toHaveLength(0);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe('decorateClickMap', () => {
|
|
112
|
-
it('draws one badge per matched anchor, highlights the active row and cleans up after itself', () => {
|
|
113
|
-
const frameDocument = createDocument();
|
|
114
|
-
const onPlaced = jest.fn();
|
|
115
|
-
const onSelect = jest.fn();
|
|
116
|
-
const items = [item('button', PROMO, 'Перейти на страницу акции', 1631), item('image', PROMO, '', 172), item('gone', 'https://old.example.com/', 'x', 5)];
|
|
117
|
-
const cleanup = decorateClickMap(frameDocument, {
|
|
118
|
-
items,
|
|
119
|
-
activeKey: 'button',
|
|
120
|
-
onPlaced,
|
|
121
|
-
onSelect
|
|
122
|
-
});
|
|
123
|
-
const badges = [...frameDocument.querySelectorAll(`[${CLICK_MAP_BADGE_ATTRIBUTE}]`)];
|
|
124
|
-
expect(badges.map(badge => [badge.getAttribute(CLICK_MAP_BADGE_ATTRIBUTE), badge.textContent])).toEqual([['button', '1.6K'], ['image', '172']]);
|
|
125
|
-
expect(badges[0].style.backgroundColor).not.toBe(badges[1].style.backgroundColor);
|
|
126
|
-
expect(frameDocument.querySelector('a strong')?.parentElement?.style.outline).toContain('solid');
|
|
127
|
-
expect(onPlaced).toHaveBeenCalledWith({
|
|
128
|
-
placed: ['button', 'image'],
|
|
129
|
-
pooled: [],
|
|
130
|
-
unplaced: ['gone']
|
|
131
|
-
});
|
|
132
|
-
badges[1].click();
|
|
133
|
-
expect(onSelect).toHaveBeenCalledWith('image');
|
|
134
|
-
cleanup();
|
|
135
|
-
expect(frameDocument.querySelectorAll(`[${CLICK_MAP_BADGE_ATTRIBUTE}]`)).toHaveLength(0);
|
|
136
|
-
expect(frameDocument.querySelector('a strong')?.parentElement?.style.outline).toBe('');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
describe('decorateClickMap in heat mode', () => {
|
|
140
|
-
it('tints every matched anchor by its share of clicks and counts only the active one', () => {
|
|
141
|
-
const frameDocument = createDocument();
|
|
142
|
-
const items = [item('button', PROMO, 'Перейти на страницу акции', 1000), item('image', PROMO, '', 250), item('gone', 'https://old.example.com/', 'x', 5)];
|
|
143
|
-
const cleanup = decorateClickMap(frameDocument, {
|
|
144
|
-
items,
|
|
145
|
-
mode: 'heat',
|
|
146
|
-
activeKey: 'image'
|
|
147
|
-
});
|
|
148
|
-
const heats = [...frameDocument.querySelectorAll(`[${CLICK_MAP_HEAT_ATTRIBUTE}]`)];
|
|
149
|
-
const badges = [...frameDocument.querySelectorAll(`[${CLICK_MAP_BADGE_ATTRIBUTE}]`)];
|
|
150
|
-
const alpha = heat => Number(heat.style.backgroundColor.match(/[\d.]+\)$/)?.[0].slice(0, -1));
|
|
151
|
-
expect(heats.map(heat => heat.getAttribute(CLICK_MAP_HEAT_ATTRIBUTE))).toEqual(['button', 'image']);
|
|
152
|
-
expect(alpha(heats[0])).toBeGreaterThan(alpha(heats[1]));
|
|
153
|
-
expect(badges.map(badge => [badge.getAttribute(CLICK_MAP_BADGE_ATTRIBUTE), badge.textContent])).toEqual([['image', '250']]);
|
|
154
|
-
cleanup();
|
|
155
|
-
expect(frameDocument.querySelectorAll(`[${CLICK_MAP_HEAT_ATTRIBUTE}], [${CLICK_MAP_BADGE_ATTRIBUTE}]`)).toHaveLength(0);
|
|
156
|
-
});
|
|
157
|
-
it('selects a row by clicking its heat overlay', () => {
|
|
158
|
-
const frameDocument = createDocument();
|
|
159
|
-
const onSelect = jest.fn();
|
|
160
|
-
decorateClickMap(frameDocument, {
|
|
161
|
-
items: [item('text', PROMO, 'странице акции', 1)],
|
|
162
|
-
mode: 'heat',
|
|
163
|
-
onSelect
|
|
164
|
-
});
|
|
165
|
-
frameDocument.querySelector(`[${CLICK_MAP_HEAT_ATTRIBUTE}]`)?.click();
|
|
166
|
-
expect(onSelect).toHaveBeenCalledWith('text');
|
|
167
|
-
});
|
|
168
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { ExternalImagePreview } from '../ExternalImagePreview';
|
|
4
|
-
describe('ExternalImagePreview', () => {
|
|
5
|
-
it('sizes the image by CSS, not by width/height attributes', () => {
|
|
6
|
-
render(_jsx(ExternalImagePreview, {
|
|
7
|
-
src: "https://example.com/a.png",
|
|
8
|
-
width: 120,
|
|
9
|
-
height: 80,
|
|
10
|
-
placeholder: _jsx("i", {})
|
|
11
|
-
}));
|
|
12
|
-
const img = screen.getByRole('img');
|
|
13
|
-
expect(img).not.toHaveAttribute('width');
|
|
14
|
-
expect(img).not.toHaveAttribute('height');
|
|
15
|
-
expect(getComputedStyle(img).width).toBe('120px');
|
|
16
|
-
expect(getComputedStyle(img).height).toBe('80px');
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { MAX_CAROUSEL_SLIDES } from '../constants';
|
|
2
|
-
import { getEmptyLayout, getPreviewContent, getPreviewTexts, getValidSlides, isEmptyContent, resolveInboxLayout } from '../helpers';
|
|
3
|
-
const HERO = 'https://cdn.example.com/hero.jpg';
|
|
4
|
-
const SLIDE = {
|
|
5
|
-
imageUrl: 'https://cdn.example.com/slide.jpg'
|
|
6
|
-
};
|
|
7
|
-
describe('resolveInboxLayout', () => {
|
|
8
|
-
it('renders the requested layout when it can', () => {
|
|
9
|
-
expect(resolveInboxLayout({
|
|
10
|
-
layoutType: 'classic',
|
|
11
|
-
title: 't',
|
|
12
|
-
body: 'b'
|
|
13
|
-
})).toBe('classic');
|
|
14
|
-
expect(resolveInboxLayout({
|
|
15
|
-
layoutType: 'banner',
|
|
16
|
-
bannerUrl: HERO
|
|
17
|
-
})).toBe('banner');
|
|
18
|
-
expect(resolveInboxLayout({
|
|
19
|
-
layoutType: 'captioned',
|
|
20
|
-
title: 't',
|
|
21
|
-
body: 'b',
|
|
22
|
-
bannerUrl: HERO
|
|
23
|
-
})).toBe('captioned');
|
|
24
|
-
expect(resolveInboxLayout({
|
|
25
|
-
layoutType: 'carousel',
|
|
26
|
-
title: 't',
|
|
27
|
-
body: 'b',
|
|
28
|
-
carousel: [SLIDE]
|
|
29
|
-
})).toBe('carousel');
|
|
30
|
-
});
|
|
31
|
-
it('degrades banner and captioned without a hero image into classic', () => {
|
|
32
|
-
expect(resolveInboxLayout({
|
|
33
|
-
layoutType: 'banner'
|
|
34
|
-
})).toBe('classic');
|
|
35
|
-
expect(resolveInboxLayout({
|
|
36
|
-
layoutType: 'captioned',
|
|
37
|
-
title: 't',
|
|
38
|
-
body: 'b'
|
|
39
|
-
})).toBe('classic');
|
|
40
|
-
});
|
|
41
|
-
it('degrades captioned and carousel without title or body into classic', () => {
|
|
42
|
-
expect(resolveInboxLayout({
|
|
43
|
-
layoutType: 'captioned',
|
|
44
|
-
body: 'b',
|
|
45
|
-
bannerUrl: HERO
|
|
46
|
-
})).toBe('classic');
|
|
47
|
-
expect(resolveInboxLayout({
|
|
48
|
-
layoutType: 'captioned',
|
|
49
|
-
title: 't',
|
|
50
|
-
bannerUrl: HERO
|
|
51
|
-
})).toBe('classic');
|
|
52
|
-
expect(resolveInboxLayout({
|
|
53
|
-
layoutType: 'carousel',
|
|
54
|
-
body: 'b',
|
|
55
|
-
carousel: [SLIDE]
|
|
56
|
-
})).toBe('classic');
|
|
57
|
-
expect(resolveInboxLayout({
|
|
58
|
-
layoutType: 'carousel',
|
|
59
|
-
title: 't',
|
|
60
|
-
carousel: [SLIDE]
|
|
61
|
-
})).toBe('classic');
|
|
62
|
-
});
|
|
63
|
-
it('degrades a carousel without valid slides into classic', () => {
|
|
64
|
-
expect(resolveInboxLayout({
|
|
65
|
-
layoutType: 'carousel',
|
|
66
|
-
title: 't',
|
|
67
|
-
body: 'b'
|
|
68
|
-
})).toBe('classic');
|
|
69
|
-
expect(resolveInboxLayout({
|
|
70
|
-
layoutType: 'carousel',
|
|
71
|
-
title: 't',
|
|
72
|
-
body: 'b',
|
|
73
|
-
carousel: [{
|
|
74
|
-
caption: 'no image'
|
|
75
|
-
}]
|
|
76
|
-
})).toBe('classic');
|
|
77
|
-
});
|
|
78
|
-
it('uses the icon as the hero fallback', () => {
|
|
79
|
-
expect(resolveInboxLayout({
|
|
80
|
-
layoutType: 'banner',
|
|
81
|
-
iconUrl: HERO
|
|
82
|
-
})).toBe('banner');
|
|
83
|
-
});
|
|
84
|
-
it('applies the SDK heuristic to unknown or absent layouts', () => {
|
|
85
|
-
expect(resolveInboxLayout({
|
|
86
|
-
bannerUrl: HERO
|
|
87
|
-
})).toBe('banner');
|
|
88
|
-
expect(resolveInboxLayout({
|
|
89
|
-
title: 't',
|
|
90
|
-
body: 'b',
|
|
91
|
-
bannerUrl: HERO
|
|
92
|
-
})).toBe('captioned');
|
|
93
|
-
expect(resolveInboxLayout({
|
|
94
|
-
title: 't',
|
|
95
|
-
body: 'b'
|
|
96
|
-
})).toBe('classic');
|
|
97
|
-
expect(resolveInboxLayout({
|
|
98
|
-
layoutType: 'video',
|
|
99
|
-
title: 't',
|
|
100
|
-
body: 'b'
|
|
101
|
-
})).toBe('classic');
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe('getValidSlides', () => {
|
|
105
|
-
it('drops slides without an image and keeps at most the SDK limit', () => {
|
|
106
|
-
const slides = [...Array.from({
|
|
107
|
-
length: MAX_CAROUSEL_SLIDES + 2
|
|
108
|
-
}, (_, index) => ({
|
|
109
|
-
imageUrl: `https://cdn.example.com/${index}.jpg`
|
|
110
|
-
})), {
|
|
111
|
-
caption: 'no image'
|
|
112
|
-
}];
|
|
113
|
-
const valid = getValidSlides(slides);
|
|
114
|
-
expect(valid).toHaveLength(MAX_CAROUSEL_SLIDES);
|
|
115
|
-
expect(valid.every(slide => !!slide.imageUrl)).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
describe('getPreviewContent', () => {
|
|
119
|
-
const en = {
|
|
120
|
-
title: 'en'
|
|
121
|
-
};
|
|
122
|
-
const de = {
|
|
123
|
-
title: 'de'
|
|
124
|
-
};
|
|
125
|
-
it('prefers the explicit content over the localized map', () => {
|
|
126
|
-
expect(getPreviewContent(de, {
|
|
127
|
-
en
|
|
128
|
-
}, 'en')).toBe(de);
|
|
129
|
-
});
|
|
130
|
-
it('picks by language with default/en/first fallbacks', () => {
|
|
131
|
-
expect(getPreviewContent(null, {
|
|
132
|
-
en,
|
|
133
|
-
de
|
|
134
|
-
}, 'de')).toBe(de);
|
|
135
|
-
expect(getPreviewContent(null, {
|
|
136
|
-
en,
|
|
137
|
-
de
|
|
138
|
-
}, 'fr')).toBe(en);
|
|
139
|
-
expect(getPreviewContent(null, {
|
|
140
|
-
de
|
|
141
|
-
}, 'fr')).toBe(de);
|
|
142
|
-
expect(getPreviewContent(null, undefined, 'en')).toBeNull();
|
|
143
|
-
expect(getPreviewContent(null, {}, 'en')).toBeNull();
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
describe('empty state', () => {
|
|
147
|
-
it('detects content with nothing to render', () => {
|
|
148
|
-
expect(isEmptyContent({
|
|
149
|
-
layoutType: 'captioned'
|
|
150
|
-
})).toBe(true);
|
|
151
|
-
expect(isEmptyContent({
|
|
152
|
-
carousel: [{
|
|
153
|
-
caption: 'no image'
|
|
154
|
-
}]
|
|
155
|
-
})).toBe(true);
|
|
156
|
-
expect(isEmptyContent({
|
|
157
|
-
title: 't'
|
|
158
|
-
})).toBe(false);
|
|
159
|
-
expect(isEmptyContent({
|
|
160
|
-
bannerUrl: 'https://cdn.example.com/hero.jpg'
|
|
161
|
-
})).toBe(false);
|
|
162
|
-
});
|
|
163
|
-
it('shows the placeholder of the requested layout, classic for unknown', () => {
|
|
164
|
-
expect(getEmptyLayout({
|
|
165
|
-
layoutType: 'banner'
|
|
166
|
-
})).toBe('banner');
|
|
167
|
-
expect(getEmptyLayout({
|
|
168
|
-
layoutType: 'video'
|
|
169
|
-
})).toBe('classic');
|
|
170
|
-
expect(getEmptyLayout({})).toBe('classic');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
describe('getPreviewTexts', () => {
|
|
174
|
-
it('moves the body into the title slot for classic without a title', () => {
|
|
175
|
-
expect(getPreviewTexts({
|
|
176
|
-
body: 'b'
|
|
177
|
-
}, 'classic')).toEqual({
|
|
178
|
-
title: 'b',
|
|
179
|
-
body: ''
|
|
180
|
-
});
|
|
181
|
-
expect(getPreviewTexts({
|
|
182
|
-
title: 't',
|
|
183
|
-
body: 'b'
|
|
184
|
-
}, 'classic')).toEqual({
|
|
185
|
-
title: 't',
|
|
186
|
-
body: 'b'
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
it('keeps the fields as-is for media layouts', () => {
|
|
190
|
-
expect(getPreviewTexts({
|
|
191
|
-
body: 'b'
|
|
192
|
-
}, 'captioned')).toEqual({
|
|
193
|
-
title: '',
|
|
194
|
-
body: 'b'
|
|
195
|
-
});
|
|
196
|
-
expect(getPreviewTexts({}, 'classic')).toEqual({
|
|
197
|
-
title: '',
|
|
198
|
-
body: ''
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
-
import { NativeRichMediaPreview } from '../NativeRichMediaPreview';
|
|
4
|
-
const lightContent = {
|
|
5
|
-
displayType: 'modal',
|
|
6
|
-
modal: {
|
|
7
|
-
showClose: true,
|
|
8
|
-
dimBackground: true,
|
|
9
|
-
background: '#FFFFFFFF',
|
|
10
|
-
title: {
|
|
11
|
-
text: 'Hi',
|
|
12
|
-
color: '#4B5057FF'
|
|
13
|
-
},
|
|
14
|
-
buttons: []
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
const darkableContent = {
|
|
18
|
-
...lightContent,
|
|
19
|
-
modal: {
|
|
20
|
-
...lightContent.modal,
|
|
21
|
-
dark: {
|
|
22
|
-
background: '#14161AFF',
|
|
23
|
-
title: {
|
|
24
|
-
color: '#F2F4F7FF'
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const themeChip = () => screen.getByLabelText('Switch light and dark');
|
|
30
|
-
const renderPreview = (props = {}) => render(_jsx(NativeRichMediaPreview, {
|
|
31
|
-
content: darkableContent,
|
|
32
|
-
width: 304,
|
|
33
|
-
height: 659,
|
|
34
|
-
withThemeToggle: true,
|
|
35
|
-
...props
|
|
36
|
-
}));
|
|
37
|
-
describe('NativeRichMediaPreview theme toggle', () => {
|
|
38
|
-
it('keeps its own theme when uncontrolled', () => {
|
|
39
|
-
renderPreview();
|
|
40
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'false');
|
|
41
|
-
fireEvent.click(themeChip());
|
|
42
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
|
|
43
|
-
});
|
|
44
|
-
it('follows the theme prop instead of a click when controlled', () => {
|
|
45
|
-
const onThemeChange = jest.fn();
|
|
46
|
-
const {
|
|
47
|
-
rerender
|
|
48
|
-
} = renderPreview({
|
|
49
|
-
theme: 'dark',
|
|
50
|
-
onThemeChange
|
|
51
|
-
});
|
|
52
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
|
|
53
|
-
fireEvent.click(themeChip());
|
|
54
|
-
expect(onThemeChange).toHaveBeenCalledWith('light');
|
|
55
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
|
|
56
|
-
rerender(_jsx(NativeRichMediaPreview, {
|
|
57
|
-
content: darkableContent,
|
|
58
|
-
width: 304,
|
|
59
|
-
height: 659,
|
|
60
|
-
withThemeToggle: true,
|
|
61
|
-
theme: "light",
|
|
62
|
-
onThemeChange: onThemeChange
|
|
63
|
-
}));
|
|
64
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'false');
|
|
65
|
-
});
|
|
66
|
-
it('reports the controlled theme even when the content has no dark overlay', () => {
|
|
67
|
-
renderPreview({
|
|
68
|
-
content: lightContent,
|
|
69
|
-
theme: 'dark',
|
|
70
|
-
onThemeChange: jest.fn()
|
|
71
|
-
});
|
|
72
|
-
expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
|
|
73
|
-
expect(themeChip()).not.toBeDisabled();
|
|
74
|
-
});
|
|
75
|
-
it('disables the chip on content without a dark overlay when uncontrolled', () => {
|
|
76
|
-
renderPreview({
|
|
77
|
-
content: lightContent
|
|
78
|
-
});
|
|
79
|
-
expect(themeChip()).toBeDisabled();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { PageHeader } from '../components/PageHeader';
|
|
4
|
-
import { PageHeaderSkeleton } from '../components/PageHeaderSkeleton';
|
|
5
|
-
describe('PageHeader', () => {
|
|
6
|
-
it('spaces the more-actions box only when there are actions', () => {
|
|
7
|
-
const {
|
|
8
|
-
rerender
|
|
9
|
-
} = render(_jsx(PageHeader, {
|
|
10
|
-
title: "Page",
|
|
11
|
-
moreActions: _jsx("span", {
|
|
12
|
-
children: "More"
|
|
13
|
-
}),
|
|
14
|
-
onSubmit: jest.fn()
|
|
15
|
-
}));
|
|
16
|
-
expect(getComputedStyle(screen.getByText('More').parentElement).marginRight).toBe('16px');
|
|
17
|
-
rerender(_jsx(PageHeader, {
|
|
18
|
-
title: "Page",
|
|
19
|
-
moreActions: _jsx("span", {
|
|
20
|
-
children: "More"
|
|
21
|
-
})
|
|
22
|
-
}));
|
|
23
|
-
expect(getComputedStyle(screen.getByText('More').parentElement).marginRight).toBe('0px');
|
|
24
|
-
});
|
|
25
|
-
it('sizes the skeleton blocks without width/height attributes', () => {
|
|
26
|
-
const {
|
|
27
|
-
container
|
|
28
|
-
} = render(_jsx(PageHeaderSkeleton, {}));
|
|
29
|
-
const sized = Array.from(container.querySelectorAll('div')).filter(el => getComputedStyle(el).width === '68px');
|
|
30
|
-
expect(sized).toHaveLength(2);
|
|
31
|
-
sized.forEach(el => {
|
|
32
|
-
expect(el).not.toHaveAttribute('width');
|
|
33
|
-
expect(el).not.toHaveAttribute('height');
|
|
34
|
-
expect(getComputedStyle(el).height).toBe('36px');
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { Color } from '@pushwoosh/kit-constants';
|
|
4
|
-
import { PushPreview } from '../PushPreview';
|
|
5
|
-
describe('PushPreview', () => {
|
|
6
|
-
it('wraps the extended description and keeps view off the DOM', () => {
|
|
7
|
-
const {
|
|
8
|
-
container
|
|
9
|
-
} = render(_jsx(PushPreview, {
|
|
10
|
-
view: "extended",
|
|
11
|
-
pushPreset: null
|
|
12
|
-
}));
|
|
13
|
-
const description = screen.getByText('Message is empty…');
|
|
14
|
-
expect(container.querySelector('[view]')).toBeNull();
|
|
15
|
-
expect(getComputedStyle(description).whiteSpace).toBe('normal');
|
|
16
|
-
});
|
|
17
|
-
it('greys out the silent-push texts without a `locked` attribute', () => {
|
|
18
|
-
const {
|
|
19
|
-
container
|
|
20
|
-
} = render(_jsx(PushPreview, {
|
|
21
|
-
view: "silent"
|
|
22
|
-
}));
|
|
23
|
-
expect(container.querySelector('[locked]')).toBeNull();
|
|
24
|
-
expect(getComputedStyle(screen.getByText('Silent push notification')).color.replace(/\s/g, '')).toBe(Color.LOCKED.replace(/\s/g, ''));
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|